Scenario: The script broke
You are running a service in a container. You started with podman run in a terminal. That worked for testing. Then you wrote a bash script to launch the container on boot. The script runs, but the container exits after ten seconds. You add a while loop to restart it. Now you have a zombie process eating CPU cycles when the container is healthy. You try podman generate systemd. The output is a 40-line unit file full of flags. You update the image, and the unit file breaks because the generated flags are stale.
You need a way to define the container that survives reboots, restarts on failure, integrates with systemd logging, and stays readable. You need a declarative format where you describe the desired state, not the steps to reach it. That is Podman Quadlet.
What Quadlet actually does
Quadlet is a systemd unit type that acts as a translation layer. You write a simple file with a .container extension. You drop it in a standard directory. systemd does not run the container directly. A helper binary reads your file, generates a temporary systemd unit in memory, and hands it to systemd.
This architecture gives you full systemd lifecycle management without writing complex [Service] sections. You declare the image, volumes, environment, and restart policy. Quadlet translates those declarations into the correct podman run command and systemd directives. If you edit the Quadlet file and reload systemd, the container restarts with the new configuration automatically.
The translation happens at runtime. This means you can inspect the generated unit to debug issues. You also get automatic dependency ordering. If your container depends on a network or volume defined in another Quadlet file, systemd starts those resources first.
Create a container definition
Quadlet files live in specific directories depending on scope. User-scoped containers go in ~/.config/containers/systemd/. System-scoped containers go in /etc/containers/systemd/. Never put Quadlet files in /usr/share/containers/systemd/. That directory is reserved for vendor-provided defaults shipped with packages. User modifications belong in /etc/.
Create a file named myapp.container. The filename determines the systemd unit name. A file named myapp.container becomes the unit container-myapp.service.
Here is how to define a basic container with environment variables and a custom command.
[Unit]
Description=My Web Application
# WHY: Sets the human-readable name shown in systemctl status and journalctl.
After=network-online.target
# WHY: Ensures the network is up before the container starts.
[Container]
Image=quay.io/fedora/httpd:latest
# WHY: Specifies the image. Quadlet pulls the image automatically if missing.
Environment=PORT=8080
# WHY: Injects environment variables into the container.
ExecStart=/usr/sbin/httpd -DFOREGROUND
# WHY: Overrides the image entrypoint. The process must run in foreground for systemd to track it.
Restart=always
# WHY: Tells systemd to restart the container if it exits, regardless of exit code.
The [Unit] section follows standard systemd syntax. The [Container] section uses Quadlet-specific keys. Most keys map directly to podman run flags, but the names are simplified. Image maps to the image argument. Volume maps to --volume. Network maps to --network.
Reload systemd to register the new unit. systemd ignores new files until you tell it to scan the directories.
systemctl --user daemon-reload
# WHY: Forces systemd to rescan unit directories and pick up the new Quadlet file.
Enable and start the service
Once the daemon knows about the unit, you can enable and start it. Use the --user flag for user-scoped containers. Omit the flag for system-scoped containers.
systemctl --user enable --now container-myapp.service
# WHY: Enables the unit for automatic start at boot and starts it immediately.
The unit name includes the container- prefix. This prefix prevents collisions with other systemd units. If you try to start myapp.service, systemd will fail. Always use the full generated name.
If you are managing a system-wide service, run the command without --user and ensure the Quadlet file is in /etc/containers/systemd/.
sudo systemctl enable --now container-myapp.service
# WHY: Manages the system-wide unit. Requires root privileges.
Reload the daemon before you start. The unit does not exist until the daemon scans the directory.
Verify the container
Check the service status to confirm the container is running. The status command shows the active state and recent log lines.
systemctl --user status container-myapp.service
# WHY: Displays the unit state, PID, and the last few log lines from journald.
You can also verify the container exists in Podman. The container name matches the unit name without the .service suffix.
podman ps --filter name=myapp
# WHY: Lists running containers matching the name. Confirms the container is active.
If the container is not running, check the journal for errors. Quadlet containers log to journald automatically. Use the unit name to filter logs.
journalctl --user -xeu container-myapp.service
# WHY: Shows detailed logs for the unit with explanatory text and jumps to the end.
Check the status before you restart. The error is usually in the journal, not the unit file.
Common pitfalls
Naming conventions cause the most confusion. The file is myapp.container. The unit is container-myapp.service. The container name inside Podman is myapp. If you type systemctl start myapp, systemd fails. If you type podman start myapp, it works, but you bypass systemd management. Stick to the unit name for lifecycle operations.
Permissions matter. systemd requires unit files to be readable by the user running the service. If you copy a file with restrictive permissions, systemd ignores it. Ensure the file is readable.
chmod 644 ~/.config/containers/systemd/myapp.container
# WHY: Sets permissions so systemd can read the file. 644 is standard for config files.
User versus system scope is a frequent trap. If you create a file in /etc/containers/systemd/ but try to manage it with systemctl --user, it fails. The directories map to the scopes. ~/.config/ is user. /etc/ is system.
Quadlet supports volumes and networks as separate unit types. You can define a volume in a .volume file and reference it in the container. This keeps storage configuration separate from container configuration.
[Volume]
Driver=vfs
# WHY: Specifies the volume driver. vfs is the default for rootless Podman.
Create mydata.volume in the same directory. Reference it in the container file with Volume=mydata:/data. systemd creates the volume before starting the container.
Use podman quadlet print to debug. This command shows the generated systemd unit that Quadlet produces from your file. It reveals exactly what systemd sees.
podman quadlet print myapp.container
# WHY: Outputs the translated systemd unit. Useful for debugging complex configurations.
Print the unit when the behavior doesn't match your expectations. The generated unit shows the exact flags passed to Podman.
When to use Quadlet
Use Quadlet when you want declarative container management integrated with systemd. Use podman run when you are testing interactively or running a one-off task. Use podman generate systemd when you need a static unit file for a legacy system that lacks Quadlet support. Use Docker Compose with Podman when you have a multi-container application defined in a compose.yaml file. Stay on Quadlet if you prefer simple text files over YAML and want native systemd features.
Trust the package manager. Manual file edits drift, snapshots stay.