| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64 |
| Applies to | Any Linux host with Podman 4.6+ and systemd |
| Privilege | sudo for rootful examples on the lab host |
| Scope | Task-oriented [Container] directive reference — image, naming, exec, environment, paths, volumes, mounts, networks, ports, user identity, user namespaces, health checks, Notify, CgroupsMode, resource limits, PodmanArgs, and where [Service] directives belong. Does not cover every podman-container.unit(5) key, rootless setup, .pod orchestration, volume or network creation depth, auto-update, or full troubleshooting. |
You already know Quadlet turns .container files into systemd services from Podman Quadlet with systemd. This guide focuses on the [Container] section — the directives you reach for when wiring image, storage, network, ports, identity, health, and readiness into a production unit.
The examples use rootful units under /etc/containers/systemd/. The same [Container] keys apply to rootless Quadlet files; only paths and systemctl --user differ.
Minimal .container file
A working image-based unit needs only an image reference:
[Container]
Image=quay.io/podman/helloSave it as hello.container under /etc/containers/systemd/, reload systemd, and start the generated service:
sudo systemctl daemon-reloadStart the generated unit:
sudo systemctl start hello.serviceThe hello image prints once and exits, so hello.service may show inactive (dead) shortly after start. That is normal for a one-shot smoke test. A long-running workload needs a process that stays up — the web example below uses httpd-24 for that reason.
For a normal image-based .container unit, Image= is the only required [Container] key. Rootfs= is an alternative for rootfs-based containers and cannot be combined with Image=.
Complete practical .container example
This lab unit wires network, volume, environment, port publishing, and health readiness together:
[Unit]
Description=Web application
After=network-online.target
[Container]
Image=registry.access.redhat.com/ubi9/httpd-24
ContainerName=web
Environment=APP_ENV=production
EnvironmentFile=/etc/web/web.env
Network=web.network
Volume=web-data.volume:/var/lib/web
Volume=/srv/web/config:/etc/web-config:Z,ro
PublishPort=18080:8080
User=0
HealthCmd=/bin/sh -c "curl -s -o /dev/null http://127.0.0.1:8080/"
HealthInterval=30s
HealthTimeout=5s
HealthRetries=3
Notify=healthy
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=multi-user.targetCompanion Quadlet files on the lab host:
web.network:
[Network]
NetworkName=web-netweb-data.volume:
[Volume]
VolumeName=web-dataEnvironment file at /etc/web/web.env:
APP_ENV=production
LOG_LEVEL=infoThe sections below walk through each directive using this unit. Network and volume creation depth lives in Podman Quadlet volume and network units when those articles ship; here we focus on how a .container references them.
Choose the image with Image=
Pin the container image in [Container]:
[Container]
Image=registry.access.redhat.com/ubi9/httpd-24:1.0Image selection tips:
- Fully qualified registry names are preferable for reproducibility
- Tags are mutable — the same tag can point at different digests later
- Digests are immutable when you need a fixed root filesystem
- Another Quadlet unit can be referenced:
Image=web.imageadds a generator dependency onweb-image.service
Image pull policy and .image unit behavior are out of scope here.
Set the container name
Without ContainerName=, Quadlet names the Podman container systemd-<unit-basename>:
hello.container → hello.service → systemd-hello containerSet an explicit name when scripts or operators expect a fixed container identity:
[Container]
ContainerName=webResult:
web.container → web.service → web containerDo not confuse ContainerName= with ServiceName=. On Podman 5.8.2, web.container generates web.service. Newer Quadlet versions also provide ServiceName= to override the generated service name, so check your installed version before relying on filename-only naming.
Override the image command with Exec=
Exec= replaces the image default command, similar to arguments after the image on podman run:
[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal
Exec=sleep 3600That becomes the equivalent of:
podman run registry.access.redhat.com/ubi9/ubi-minimal sleep 3600Exec= is the container process command. It is not systemd ExecStart= — the generator owns ExecStart and embeds the full podman run line there.
Set environment variables
Inline variables use Environment=:
Environment=APP_ENV=production LOG_LEVEL=infoLoad variables from a file with EnvironmentFile=:
EnvironmentFile=/etc/web/web.envEnvironment=sets variables directly in the unitEnvironmentFile=loads key/value pairs from a host file- The file must exist before the service starts
After web.service is running, confirm the variables inside the container:
sudo podman exec web printenv APP_ENV LOG_LEVELSample output:
production
infoBoth values appear — APP_ENV from Environment= and LOG_LEVEL from EnvironmentFile=.
Relative files and Quadlet paths
When a referenced path begins with ., Quadlet resolves it relative to the Quadlet file location:
EnvironmentFile=./web.envPlace web.env beside web.container in /etc/containers/systemd/ (or your chosen Quadlet directory).
Paths beginning with % are treated as systemd specifiers, not literal filenames. Use ./ when you intend a relative file next to the unit. Bind-mount sources that start with . follow the same resolution rule.
Mount volumes
Reference a Quadlet volume unit — the generator adds a systemd dependency automatically:
Volume=web-data.volume:/var/lib/webBind-mount a host directory:
Volume=/srv/web/config:/etc/web-config:Z,roWhen the source ends in .volume, Quadlet resolves the corresponding volume unit and wires web-data-volume.service ahead of web.service. On the lab host, the generated unit shows:
Requires=web-data-volume.service
After=web-data-volume.serviceSELinux :z / :Z labels, ownership, and backup workflows belong in Podman volume permissions and storage guides — not duplicated here.
Use Mount= for structured mount syntax
Mount= maps to Podman --mount when you need explicit key/value mount options:
Mount=type=bind,src=/srv/web,dst=/data,roQuadlet also accepts .volume and .image references in Mount= where your Podman version supports them. Prefer Volume= for the common named-volume and bind-mount cases; use Mount= when --mount syntax is clearer. The Podman volume vs bind mount guide covers CLI -v versus --mount trade-offs.
Attach the container to a network
Reference a Quadlet network unit:
Network=web.networkOr attach to an existing Podman network by name:
Network=app-networkNetwork modes work the same as podman run --network:
Network=hostNetwork=noneA .network reference creates an implicit dependency. The lab web.service unit includes Requires=web-network.service and After=web-network.service without manual [Unit] lines. Network creation and DNS belong in Podman networking modes.
Publish ports
Map host ports to container ports:
PublishPort=18080:8080Multiple mappings:
PublishPort=8080:80
PublishPort=8443:443Bind to a host IP:
PublishPort=127.0.0.1:8080:80This maps to podman run -p. The lab unit publishes host 18080 to container 8080 because UBI httpd-24 listens on 8080 inside the container.
Verify from the host:
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18080/Sample output:
403HTTP 403 confirms the port mapping works — httpd is responding. Full -p syntax and pod-level publishing rules live in Podman port mapping and Podman pod networking.
Run as a container user
Set the UID inside the container:
User=10001Add a group:
User=10001
Group=10001These control the identity inside the container namespace, not the Linux account that runs the systemd service on the host.
Do not use [Service] User= to make a rootful Quadlet rootless
This trap deserves its own warning. Do not write:
[Service]
User=podtestor:
DynamicUser=yesexpecting a rootful Quadlet under /etc/containers/systemd/ to run as an unprivileged host user. Quadlet does not support User=, Group=, or DynamicUser= in [Service] for that purpose.
For rootless workloads:
- Sign in as the target Linux account
- Place the
.containerfile in a rootless Quadlet search path such as~/.config/containers/systemd/ - Manage the unit with
systemctl --user
See Rootless Podman Quadlet for paths, lingering, and boot behavior.
Configure user namespaces
Map container user IDs with UserNS=:
UserNS=keep-idOr pin mapped IDs:
UserNS=keep-id:uid=1000,gid=1000UserNS= controls how host and container user IDs relate. Full mode semantics and volume permission interactions belong in Podman user namespaces.
Health checks
Health directives run inside the container:
HealthCmd=/bin/sh -c "curl -s -o /dev/null http://127.0.0.1:8080/"
HealthInterval=30s
HealthTimeout=5s
HealthRetries=3Use the container port (8080 here), not the published host port (18080). Avoid curl -f when the application returns a non-2xx code on success — UBI httpd returns 403 on the default page, which makes curl -f fail even when the server is up.
Run the check manually:
sudo podman healthcheck run webSample output:
healthyInspect persisted health state:
sudo podman inspect web --format '{{.State.Health.Status}}'Sample output:
healthyA health check proves the process responds inside the container. Service readiness is a separate concern — see Notify= next.
Notify= and systemd readiness
Quadlet .container services normally use systemd Type=notify. Podman and conmon can signal startup through sdnotify integration.
Wait for the container application to send READY=1:
Notify=trueWait for Podman's health state to become healthy:
Notify=healthyThe lab unit uses Notify=healthy. The generated podman run line includes --sdnotify=healthy, and web.service stays in activating until the health check passes.
| Value | Readiness signal |
|---|---|
| (default conmon integration) | conmon notifies when the container process starts |
Notify=true |
the application inside the container sends sdnotify READY=1 |
Notify=healthy |
Podman health state must be healthy before the unit is active |
Notify=healthy matters when downstream units or auto-update rollback should wait for a real health signal, not merely a running PID.
Resource limits
Set common container limits in [Container]:
Memory=512M
PidsLimit=512Quadlet can combine Podman container limits with normal systemd cgroup controls in [Service]. CPU throttling may use supported Podman keys on your version or systemd CPUQuota= / MemoryMax= in [Service] when that fits your deployment.
Do not duplicate the full cgroup tuning article here — apply limits that match your workload and verify with systemd-cgtop or podman stats.
Quadlet CgroupsMode=split default
The normal podman run CLI default is enabled. Quadlet .container units default to split:
CgroupsMode=splitsplit places conmon and the container payload in separate cgroups, which suits systemd service management and cgroup delegation.
Inspect the generated command:
sudo systemctl show -p ExecStart web.serviceSample output (trimmed):
ExecStart={ path=/usr/bin/podman ; argv[]=/usr/bin/podman run --name web --replace --rm --cgroups=split --network web-net --sdnotify=healthy ...--cgroups=split appears even when you omit CgroupsMode= in the source file.
For containers inside a Quadlet .pod where pod-level cgroup limits must apply, consider enabled or no-conmon instead. Do not override the default without a reason.
Restart and systemd options belong in [Service]
Container restart policy belongs in systemd, not in podman run --restart:
[Service]
Restart=always
RestartSec=5
TimeoutStartSec=300These are ordinary systemd directives passed through by the generator. Long image pulls may need a higher TimeoutStartSec on first start.
Do not force every systemd option into [Container]. Keep Podman-specific settings under [Container] and service behavior under [Service].
Use PodmanArgs= only as an escape hatch
When Quadlet has no dedicated key yet, append raw podman run flags:
PodmanArgs=--some-new-option=valuePodmanArgs= appends to the generated command. Prefer native directives whenever they exist:
- the generator can infer dependencies for
Volume=,Network=, andImage= - arbitrary flags may conflict with dedicated keys
- units become harder to read and review
Reach for PodmanArgs= only for new Podman flags that lack a Quadlet equivalent on your version.
Inspect the generated podman run command
Read the full generated unit:
sudo systemctl cat web.serviceSample output (trimmed):
Requires=web-network.service
After=web-network.service
Requires=web-data-volume.service
After=web-data-volume.service
[X-Container]
Image=registry.access.redhat.com/ubi9/httpd-24
ContainerName=web
PublishPort=18080:8080
...
ExecStart=/usr/bin/podman run --name web --replace --rm --cgroups=split ...Or print only ExecStart:
sudo systemctl show -p ExecStart web.serviceThis is the fastest way to see how [Container] directives translate on your Podman version. Generator failures and missing units belong in Podman Quadlet troubleshooting.
Common .container configuration errors
| Symptom | Likely cause | Fix |
|---|---|---|
Service stuck in activating with Notify=healthy |
Health command fails or uses wrong port | Fix HealthCmd; use container port, not host port |
curl -f health check always fails |
Application returns non-2xx on success | Drop -f or accept the real success code |
Container named systemd-web unexpectedly |
No ContainerName= set |
Add ContainerName= or expect the systemd- prefix |
| Network or volume not ready | Referenced .network / .volume missing or misnamed |
Match Quadlet filenames; check generator Requires= lines |
[Service] User= ignored for rootless behavior |
Rootful path with systemd user drop-in | Move unit to rootless Quadlet paths |
References
- Podman: Container Quadlet reference
- Podman: systemd units using Quadlet
- Podman: Network Quadlet reference
- Podman: Volume Quadlet reference
- RHEL 10: Porting containers to systemd using Podman
Summary
A .container Quadlet file is a task layer on top of podman run, not a copy of the full podman-container.unit(5) manual. For an image-based .container unit, Image= is the only required [Container] key; Rootfs= is the alternative for rootfs-based containers. ContainerName= controls the Podman container identity separately from web.service. Environment, volumes, networks, and ports use dedicated directives — and references ending in .volume or .network create systemd dependencies you do not need to wire by hand.
Keep restart policy, timeouts, and boot targets in [Service] and [Install]. Do not use [Service] User= to fake rootless behavior on a rootful unit. Quadlet defaults to CgroupsMode=split, which differs from the CLI — check systemctl show -p ExecStart when cgroup layout matters. Use Notify=healthy when systemd should wait for a real health signal, and reserve PodmanArgs= for flags that have no first-class Quadlet key yet.
If you are new to the generator model, start with Podman Quadlet with systemd. For rootless paths and lingering, continue with Rootless Podman Quadlet.

