| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64 |
| Applies to | Linux hosts with Podman Quadlet support, systemd user sessions, and cgroup v2 |
| Privilege | Normal user for Quadlet work; sudo for loginctl linger and administrator paths |
| Scope | Rootless Quadlet lifecycle — user search paths, systemctl --user, journalctl --user, linger for boot and logout persistence, admin-provisioned units under /etc/containers/systemd/users/, XDG_RUNTIME_DIR, supplementary-group traps, rootless storage, and boot troubleshooting. Assumes working rootless Podman. Does not cover subuid setup, rootless networking, full .container directive reference, privileged-port workarounds, or generic Quadlet generator errors. |
systemctl --user. It does not teach rootless Podman setup, rootless networking, or every [Container] directive. For those topics, see rootless Podman, Podman Quadlet container file, and Podman Quadlet with systemd for the shared generator model.
You already run Podman without root. Quadlet lets you declare containers as .container files and hand lifecycle to your systemd user manager instead of PID 1. The shift is not cosmetic: paths, systemctl flags, journals, and boot behavior all change when the unit runs rootlessly.
The lab user is podtest. Every systemctl --user and podman command below runs as that account unless noted.
How rootless Quadlet differs from rootful Quadlet
Rootful Quadlet drops files where the system manager reads them:
/etc/containers/systemd/web.container
↓
system systemd manager (PID 1)
↓
systemctl / journalctl
↓
rootful PodmanRootless Quadlet uses the user manager tied to one Linux account:
~/.config/containers/systemd/web.container
↓
systemd --user (per-user manager)
↓
systemctl --user / journalctl --user
↓
rootless PodmanDo not try to run a rootful Quadlet as another user by adding [Service] overrides:
[Service]
User=podtestThat still executes under PID 1's generator. Rootless Quadlet must be placed in a rootless search path and started by that user's systemd manager. The Quadlet [Container] syntax is largely the same in rootless and rootful mode, but some directives behave differently or are restricted by rootless Podman. The main operational differences covered here are ownership, search paths, user-manager scope, linger, storage, and session environment — see Podman Quadlet container file for directive detail.
Verify rootless Podman first
Quadlet only wraps Podman. Confirm the account is already rootless before you add unit files.
As podtest, ask Podman about security mode and cgroup version:
podman info --format 'rootless={{.Host.Security.Rootless}} cgroups={{.Host.CgroupsVersion}}'Sample output:
rootless=true cgroups=v2A false rootless value means you are still on the rootful path — fix rootless Podman before continuing. Quadlet requires cgroup v2; if cgroups is not v2, resolve that before continuing.
Run a quick container smoke test:
podman run --rm quay.io/podman/helloThe hello image prints its ASCII banner and exits. That proves image pull and rootless runtime work under this account.
Rootless Quadlet search paths
Quadlet generators scan several directories. For day-to-day work you usually touch only one.
| Path | Who manages it | Typical use |
|---|---|---|
~/.config/containers/systemd/ |
The logged-in user | Personal services you edit yourself |
$XDG_CONFIG_HOME/containers/systemd/ |
Same user when XDG_CONFIG_HOME is set |
Same as above when config lives outside ~/.config |
$XDG_RUNTIME_DIR/containers/systemd/ |
User session | Ephemeral or session-scoped units |
/etc/containers/systemd/users/ |
Administrator | Quadlets distributed to all users |
/etc/containers/systemd/users/${UID}/ |
Administrator | Quadlets for one UID |
Think of the split this way:
~/.config/...— you own and maintain the declaration./etc/.../users/— an administrator ships the same unit to every user session that loads it./etc/.../users/${UID}/— an administrator targets a single service account (for example UID1014forpodtest).
Distribution packages may add vendor paths under /usr/share/containers/systemd/users/. Treat those like administrator content: useful to know, rarely the first place you create a personal app unit.
Create a rootless .container file
Create the user config directory:
mkdir -p ~/.config/containers/systemdAdd ~/.config/containers/systemd/web.container:
[Container]
Image=quay.io/podman/hello
[Install]
WantedBy=default.targetDo not run systemctl --user enable web.service. Quadlet processes the [Install] section while generating the service, so WantedBy=default.target is declared in the .container file and takes effect after systemctl --user daemon-reload.
web.container becomes web.service in the user manager. The hello image exits after printing, so the unit may show inactive (dead) soon after start — that is expected for this smoke test.
Tell the user manager to pick up the new file:
systemctl --user daemon-reloaddaemon-reload succeeds silently when the generator accepts the unit.
Start the generated service — no sudo:
systemctl --user start web.serviceCheck status under the same user manager:
systemctl --user status web.service --no-pagerSample output:
● web.service
Loaded: loaded (/home/podtest/.config/containers/systemd/web.container; generated)
Active: active (running) since Sun 2026-08-23 03:09:00 IST; 33ms ago
Main PID: 248423 (conmon)The Loaded: line must point at your home-directory .container file. Active: may flip to inactive (dead) once the hello container finishes — that only means the one-shot workload exited cleanly.
Why sudo systemctl is wrong here
sudo systemctl talks to PID 1. systemctl --user talks to your user manager. They are separate unit namespaces.
Query the system manager as root:
sudo systemctl status web.service --no-pagerSample output:
× web.service
Loaded: not-found (Reason: Unit web.service not found.)
Active: failed (Result: exit-code) since Sun 2026-08-23 03:04:38 IST; 4min agoEven when a name collides, the system unit is not your rootless Quadlet. The Loaded: path will not show /home/podtest/.config/containers/systemd/web.container.
Query the user manager instead:
systemctl --user status web.service --no-pagerSample output:
● web.service
Loaded: loaded (/home/podtest/.config/containers/systemd/web.container; generated)
Active: inactive (dead) since Sun 2026-08-23 03:09:00 IST; 13s agoThe home-directory path in Loaded: confirms you are inspecting the right manager.
The same split applies to Podman. As root:
sudo podman ps --format 'table {{.Names}}\t{{.Status}}'Root's container list is empty when only rootless workloads run.
As podtest:
podman ps --format 'table {{.Names}}\t{{.Status}}'You see containers started by that user's manager. Always match the Linux account: systemctl --user plus podman as the same user, never sudo for routine rootless checks.
Rootless Quadlet logs
Container stdout and Quadlet generator messages for user units land in the user journal.
Recent lines for the service:
journalctl --user -u web.service --no-pager -n 8Sample output:
Aug 23 03:09:00 vm1.lab.example systemd-web[248423]: Project: https://github.com/containers/podman
Aug 23 03:09:00 vm1.lab.example systemd-web[248423]: Website: https://podman.io
Aug 23 03:09:00 vm1.lab.example web[248401]: efbc7bb528af8ea6f1b746109408a95d95287472610a75f07d066b08ab539dc4
Aug 23 03:09:00 vm1.lab.example podman[248427]: container remove efbc7bb528af8ea6f1b746109408a95d95287472610a75f07d066b08ab539dc4Follow live output while you reproduce an issue:
journalctl --user -u web.service -fUse Ctrl+C to stop following. You do not need sudo to read your own user journal. Reach for sudo journalctl only when you are debugging someone else's session as an administrator.
Linger is not required just to use rootless Quadlet
A common oversimplification says you must run loginctl enable-linger before any rootless Quadlet works. That is false for interactive use.
While you have an active login and a running user systemd manager:
systemctl --user start web.serviceworks without linger. I run Quadlet units during an SSH or desktop session without enabling linger first.
Linger matters when the service must:
- start at host boot without anyone logging in
- keep running after the user's final logout
- keep the user systemd manager alive when no graphical or SSH session exists
For a container you start manually during the workday, skip linger until you need boot or logout persistence.
Enable linger for boot and logout persistence
When boot-time or post-logout behavior is required, an administrator enables linger for that account:
sudo loginctl enable-linger podtestVerify the setting:
loginctl show-user podtest -p LingerSample output:
Linger=yesWith linger and [Install] WantedBy=default.target in the Quadlet file, reload the user manager after edits — Quadlet applies the install relationship during generation; do not systemctl --user enable the generated service:
systemctl --user daemon-reloadOn the next boot, systemd starts the user manager for podtest even when nobody logs in, and units wanted by default.target can start automatically. Test on a lab host by rebooting and checking systemctl --user status from a lingering session or loginctl state — do not assume boot works until you verify on your distribution.
Disable linger
To stop keeping the user manager alive without a session:
sudo loginctl disable-linger podtestAfter disable, user services may stop when the last session ends, and the user manager might not exist at boot until someone logs in again. Disabling linger does not delete Quadlet files under ~/.config/containers/systemd/ — it only changes session policy.
Administrator-provisioned rootless Quadlet
Teams sometimes centralize unit files while still running containers rootlessly. Place the declaration where the administrator owns the file but the target user's manager executes it.
Create a UID-specific directory (1014 is podtest on the lab host):
sudo mkdir -p /etc/containers/systemd/users/1014Install web.container (or another name) there:
[Container]
Image=quay.io/podman/hello
ContainerName=admin-hello
[Install]
WantedBy=default.targetAs podtest, reload the user manager:
systemctl --user daemon-reloadConfirm the generator picked up the admin file:
systemctl --user list-unit-files 'admin-web*'Sample output:
UNIT FILE STATE PRESET
admin-web.service generated -The administrator owns /etc/containers/systemd/users/1014/admin-web.container, but admin-web.service still runs under podtest's rootless Podman. That pattern suits service accounts where config management drops units without giving users write access to /etc.
/etc/containers/systemd/users/ for all users
A Quadlet file directly under:
/etc/containers/systemd/users/can be processed by user sessions broadly — not only one UID. Use that path when every user (or every user who receives the drop-in) should get the same generated unit.
Prefer /etc/containers/systemd/users/${UID}/ when the workload belongs to one account. Dropping an application-specific unit in the all-users directory accidentally exposes it to every user manager that loads system-wide user Quadlets.
Rootless environment and XDG_RUNTIME_DIR
systemctl --user and rootless Podman expect a valid session runtime directory.
Print the variable inside a normal login shell:
echo "$XDG_RUNTIME_DIR"Sample output:
/run/user/1014Typical layout is /run/user/$UID. Without it, user systemd cannot connect. A bare su into the account often reproduces the failure:
systemctl --user status web.serviceSample output:
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not definedThat often appears when you use plain su instead of a login shell (su -, machinectl shell, or SSH). Prefer a real user session for Quadlet work. Do not paper over a broken session by exporting another user's XDG_RUNTIME_DIR — that connects you to the wrong bus.
Administrators automating checks from cron or Ansible can use machinectl shell podtest@ or systemctl --user -M podtest@ patterns on supported setups; interactive tutorials should stick to a normal login.
Supplementary groups and GroupAdd=keep-groups
Rootless Quadlet inherits the user manager's view of groups from when that manager started. This bites when a unit uses:
GroupAdd=keep-groupsand an administrator later adds the user to a new group:
sudo usermod -aG docker podtestThe running user manager may not see the new membership until you refresh the session. Terminate the user's systemd user manager:
sudo loginctl terminate-user podtestThen log in again (or reboot). Until you do, device or volume permission errors can look like Podman bugs when they are stale group data on the user manager.
Rootless port restrictions
A rootless Quadlet that publishes a privileged host port:
PublishPort=80:80hits the same low-port restriction as ordinary rootless podman run. The generator does not grant extra capability.
Use a high host port in the unit, or follow rootless Podman privileged ports for supported workarounds. This article does not duplicate those recipes.
Rootless Quadlet storage
Rootless images and container layers live in the user's store, not /var/lib/containers/storage.
Confirm paths:
podman info --format 'rootless={{.Host.Security.Rootless}} graphRoot={{.Store.GraphRoot}} runRoot={{.Store.RunRoot}}'Sample output:
rootless=true graphRoot=/home/podtest/.local/share/containers/storage runRoot=/run/user/1014/containersAdministrators hunting disk usage for a rootless Quadlet service must inspect that user's home tree and /run/user/UID, not only the rootful graph root.
Troubleshooting when the rootless service does not start at boot
Work through these checks in order when a unit runs interactively but fails after reboot or logout:
| Symptom | Likely cause | Fix |
|---|---|---|
| Unit not found in user manager | File not in a rootless search path, or typo in filename | Place name.container under ~/.config/containers/systemd/ or admin path; filename stem becomes service name |
| Generator never sees new file | No reload after edit | Run systemctl --user daemon-reload as the target user |
| Nothing starts until login | Linger disabled | sudo loginctl enable-linger USER and confirm Linger=yes |
| Service does not join default boot target | Missing install section | Add [Install] / WantedBy=default.target to the Quadlet source |
systemctl --user cannot connect |
No session / missing XDG_RUNTIME_DIR |
Log in with a real session; avoid bare su |
| Permission errors after group change | Stale supplementary groups on user manager | loginctl terminate-user and log in again |
Pull user-unit logs last — they usually name the real fault:
journalctl --user -u web.service --no-pager -n 20Generator syntax errors and missing images often appear here. For cross-cutting Quadlet failures (wrong section headers, unsupported keys), see Podman Quadlet troubleshooting.
References
- podman-systemd.unit(5) — Quadlet unit types and search paths
- systemd.unit(5) — unit file structure and install sections
- loginctl(1) — linger enablement
- Podman rootless documentation — upstream rootless tutorial
Summary
Rootless Podman Quadlet moves unit files into your home config tree (or administrator paths under /etc/containers/systemd/users/) and hands lifecycle to systemctl --user instead of PID 1. The lab web.container example shows the full loop: create the file, daemon-reload, start the service, and read logs with journalctl --user — all without sudo.
The mistake I see most often is reaching for sudo systemctl or sudo podman after a rootless unit misbehaves. Those tools query the system manager and rootful store; your Quadlet lives in the user manager tied to one account. Match the user for both systemd and Podman commands.
Linger is the other misunderstood knob. You do not need it to start a container during an active session. Enable linger only when the workload must survive logout or start at boot without a login. Add [Install] WantedBy=default.target in the Quadlet source so the generator wires the unit to default.target, reload the user manager, and verify with loginctl show-user.
For production hardening, remember rootless storage paths, low-port publishing limits, and supplementary groups frozen at user-manager start. When boot persistence still fails after linger and install sections look correct, user journals usually spell out the next fix — and generic generator issues belong in the dedicated troubleshooting guide.

