docker run to quadlet converter

Paste a docker run command, get a .container file that systemd can start at boot.

Last updated

In short: Every docker run option becomes one line in a .container file: -p becomes PublishPort=, -v becomes Volume=, -e becomes Environment= and --restart becomes Restart= in [Service]. -d and --rm are dropped because systemd and quadlet already handle them. Save the file as ~/.config/containers/systemd/<name>.container, run systemctl --user daemon-reload, then systemctl --user start <name>.service.

Your input never leaves your browser. Everything is calculated on your device; nothing you type is sent to a server.

Options

Generated files

# Converted from a docker run command.

[Unit]
Description=vaultwarden container

[Container]
Image=docker.io/vaultwarden/server:latest
ContainerName=vaultwarden
PublishPort=127.0.0.1:8000:80
Volume=/vw-data/:/data/
Environment=SIGNUPS_ALLOWED=false

[Service]
Restart=always

[Install]
WantedBy=default.target

Warnings and notes

  • Note command: -d/--detach is not needed: systemd runs the container in the background.
  • Note options: On SELinux systems (Fedora, RHEL, recent openSUSE) bind mounts need :Z or :z; enable the SELinux option to add them.

Save the files in ~/.config/containers/systemd/, run systemctl --user daemon-reload, then systemctl --user start <name>.service. Run loginctl enable-linger $USER once so the container starts at boot without a login. The files target podman 5.0 or newer unless you tick the 4.9 option.

How does the docker run to quadlet converter work?

A docker run command starts a container once. A quadlet .container file describes the same container so that systemd can own it: start it at boot, restart it when it crashes, stop it cleanly on shutdown and send its output to the journal. Podman's quadlet generator reads the file and writes a normal systemd service with the matching podman run command inside.

Most options have a key with a similar name: -p is PublishPort=, -v is Volume=, -e is Environment=, --name is ContainerName= and --health-cmd is HealthCmd=. Everything after the image name becomes Exec=. Options that podman 5.0 has no key for, such as --memory and --add-host, go into PodmanArgs=, which passes them straight to podman run.

The restart policy is the one real change in thinking. Instead of the container runtime restarting the container, systemd does it through Restart= in the [Service] section. That is also why -d and --rm disappear: systemd already runs the container in the background and removes it when it stops.

Values are escaped for systemd, so 50% becomes 50%% and $5 becomes $$5; the container still receives the original text. If a compose file describes several containers, use the docker-compose to quadlet converter instead.

Worked examples

Publish a port on localhost only

Input -p 127.0.0.1:8000:80 gives PublishPort=127.0.0.1:8000:80

The host IP, host port and container port carry over as-is, so the service is only reachable from the machine itself.

Keep a container running

Input --restart unless-stopped gives Restart=always

systemd owns the restart policy. unless-stopped and always both become Restart=always; systemctl --user stop still stops it for good.

Give a container the NVIDIA GPU

Input --gpus all gives AddDevice=nvidia.com/gpu=all

Podman uses CDI device names for GPUs. Generate the spec once with nvidia-ctk cdi generate from the NVIDIA Container Toolkit.

Frequently asked questions

How do I turn a docker run command into a systemd service with podman?

Convert it to a .container file and put it in ~/.config/containers/systemd/ (or /etc/containers/systemd/ for root). After systemctl --user daemon-reload, podman's quadlet generator creates <name>.service, which you start with systemctl --user start <name>.service. The keys are documented in the podman-systemd.unit(5) man page.

What happens to -d, --rm and -it?

-d and --rm are dropped: systemd runs the container in the background, and quadlet always removes it when the unit stops. -i and -t are kept as PodmanArgs=--interactive --tty, which only matters for images that expect a terminal.

Why does -v data:/data create a .volume file?

A name instead of a path means a named volume. The .volume file lets systemd create it before the container starts, and VolumeName=data keeps the existing volume, so your data is reused.

How do I start a rootless container at boot?

Keep WantedBy=default.target in [Install] (the Start at boot option) and run loginctl enable-linger $USER once. Without lingering, user services only run while you are logged in.

What happens to options the converter does not know?

--flag=value options are passed through as PodmanArgs= with a warning, so nothing disappears silently. Write unknown options with = (for example --blkio-weight=300); otherwise the converter cannot tell the value apart from the image name.