Podman Quadlet Basics: Let Systemd Manage Your Containers
Overview
Podman (Pod Manager) is a daemonless container engine developed by Red Hat. Compared with Docker, the biggest difference is that Podman does not require a background daemon. Containers run directly as child processes, rootless mode is supported natively, and most Docker CLI commands are compatible with it (alias docker=podman is often enough for a smooth switch).
Quadlet is Podman’s built-in systemd generator. At boot time and whenever you run systemctl daemon-reload, it reads Quadlet files from specific directories (such as .container and .pod) and automatically generates the corresponding .service unit files for Systemd to manage. In other words, you write a simple .container file, and Quadlet turns it into a complete Systemd service, so you do not need to hand-write a complex .service file yourself.
Benefits of this approach:
- Automatic start and stop: containers are managed as Systemd services and can follow system boot or user session startup/shutdown
- Dependency management: use native Systemd directives such as
Requires=andAfter=to define startup order - Log integration: container logs go directly into journald, unified with system logs
- Resource control: use Systemd cgroups to limit container resources
Official documentation: podman-systemd.unit(5). This article mainly covers rootless containers.
Some Practical Differences Between Podman (Rootless) and Docker (Rootful)
A few commonly mentioned ones are:
- There is no long-running daemon, so the day-to-day management path is more direct.
- When containers run as a regular user, the privilege boundary is usually tighter than with rootful Docker. If something goes wrong inside the container, the blast radius is often smaller too.
- It tends to be less invasive to the host network stack. It usually does not directly take over system-level bridge, NAT, and firewall rules, so it can be less troublesome on hosts with more complex networking.
- If a container mostly runs a single-UID process, rootless UID mapping can sometimes align written files with the user who launched the container, which helps reduce bind mount ownership confusion. In multi-user or multi-process images, this usually helps much less.
- When used with Quadlet, you can hand container lifecycle management directly to Systemd instead of building another layer just for auto-start.
- Image auto-updates can also be handled with Podman’s built-in mechanism, without necessarily adding a separate updater container.
These points are better treated as trade-offs than absolute advantages. Whether it is actually more convenient still depends on the image, the bind mounts, and the host networking setup.
File Types and Locations
Quadlet supports these unit file types:
| Suffix | Purpose |
|---|---|
.container |
Define a single container |
.pod |
Define a group of containers sharing networking |
.volume |
Define a Podman volume |
.network |
Define a Podman network |
.image |
Define a container image (pull policy, etc.) |
.build |
Build an image from a Containerfile/Dockerfile |
.kube |
Import Kubernetes YAML |
.artifact |
Define Podman artifacts |
The most commonly used ones are .container and .pod.
Quadlet searches for files by priority, and the paths differ between rootful and rootless mode:
Rootful mode (root user):
| Priority | Path |
|---|---|
| Highest | /run/containers/systemd/ (temporary, useful for testing) |
| Medium | /etc/containers/systemd/ (administrator configuration) |
| Lowest | /usr/share/containers/systemd/ (provided by the distribution) |
Rootless mode (regular user):
| Priority | Path |
|---|---|
| Highest | $XDG_RUNTIME_DIR/containers/systemd/ |
~/.config/containers/systemd/ (most common) |
|
/etc/containers/systemd/users/${UID}/ |
|
/etc/containers/systemd/users/ |
|
/usr/share/containers/systemd/users/${UID}/ |
|
| Lowest | /usr/share/containers/systemd/users/ |
Quadlet also supports symbolic links and systemd-style drop-in overrides (for example, foo.container.d/10-override.conf).
Single Container
All examples below assume rootless mode.
Here is a minimal .container file:
# nginx.container
[Unit]
Description=Nginx container
After=network-online.target
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=default.targetWhat each section means:
- [Unit]: Systemd metadata such as description and startup ordering dependencies
- [Container]: container configuration such as image, ports, volumes, and environment variables (processed by Quadlet)
- [Service]: Systemd service behavior, such as restart policy (passed through to Systemd)
- [Install]: installation metadata.
WantedBy=default.targetmeans auto-start when the user logs in (passed through to Systemd)
Besides Quadlet-specific sections like [Container], you can use any standard Systemd configuration options in the file.
Once the file is written, everything else is standard Systemd. In rootless mode, remember to add --user:
# Reload configuration
systemctl --user daemon-reload
# Start
systemctl --user start nginx
# Check status
systemctl --user status nginx
# Logs
journalctl --user -u nginx -f
# Stop
systemctl --user stop nginxMulti-Container Orchestration
If an application consists of multiple containers, use a .pod file to place them into the same Pod. Containers inside a Pod share networking and can talk to each other via 127.0.0.1.
# wordpress.pod
[Pod]
PublishPort=8080:80
[Install]
WantedBy=default.targetContainers join the Pod via Pod=. Since containers inside the Pod share networking, WordPress can connect directly to MySQL using 127.0.0.1:3306:
# db.container
[Container]
Image=docker.io/library/mysql:8.0
Pod=wordpress.pod
Environment=MYSQL_ROOT_PASSWORD=***
Environment=MYSQL_DATABASE=wordpress
Environment=MYSQL_USER=wpuser
Environment=MYSQL_PASSWORD=***
Volume=/path/to/data:/var/lib/mysql
[Service]
Restart=always# wp.container
[Container]
Image=docker.io/library/wordpress:php8.2-apache
Pod=wordpress.pod
Environment=WORDPRESS_DB_HOST=127.0.0.1:3306
Environment=WORDPRESS_DB_USER=wpuser
Environment=WORDPRESS_DB_PASSWORD=***
Environment=WORDPRESS_DB_NAME=wordpress
Volume=wp-content:/var/www/html/wp-content
[Service]
Restart=alwaysTo start everything, you only need to operate on the Pod:
systemctl --user start wordpress-podSystemd will automatically start all containers in the Pod and determine the startup order based on dependencies.
Migrating from Compose
If you already have a docker-compose.yml, you can use podlet to automatically convert the Compose file in the current directory: podlet -i -a compose --pod. For more usage details, check podlet --help.
Common Issues
I. Keeping Containers Running After Logout
By default, containers stop when the user logs out. To keep them running, enable linger for the current user:
sudo loginctl enable-linger $USERYou only need to do this once.
II. Making Image Pulls Compatible with docker.io
Podman does not automatically pull images from docker.io by default. If you want Docker-style image names like nginx:alpine to work, add this to /etc/containers/registries.conf:
unqualified-search-registries = ["docker.io"]After that, Podman will automatically search docker.io when it sees an image name without a registry prefix. If you always use the full docker.io/ prefix, this configuration is unnecessary.
III. Unable to Reach the Host via Public IP with Pasta Networking
Starting with Podman 5, Pasta is the default networking backend. Containers share the host’s public IP in this mode. If you need a separate internal network instead, you can configure one like this:
# ~/.config/containers/containers.conf
[network]
pasta_options = ["-a", "10.0.2.0", "-n", "24", "-g", "10.0.2.2", "--dns-forward", "10.0.2.3", "--map-gw"]
[containers]
# Allow containers to reach the host via host.containers.internal
host_containers_internal_ip = "10.0.2.2"IV. The Quadlet File Is in the Right Place but No Systemd Unit Is Generated
After updating a Quadlet file, run systemctl --user daemon-reload to regenerate the corresponding unit. If the Systemd unit still does not appear, the Quadlet configuration may contain an error. You can inspect the specific error with /usr/lib/systemd/system-generators/podman-system-generator --user --dryrun | grep <quadlet-file-name>.
V. Automatic Image Updates
Add AutoUpdate=registry to the [Container] section that should be updated automatically, then enable Podman’s auto-update timer:
systemctl --user enable --now podman-auto-update.timerVI. Writing Quadlet Configs More Conveniently
In most cases, podlet can easily convert podman run commands and Compose files into Quadlet configs, and you only need to make small adjustments afterward. But when a Compose setup becomes very complex and includes many containers, an AI agent plus Skills can help.