Podman gives every Linux user the ability to build and run containers, so the security of a Podman host depends on how user namespaces, the API socket, SELinux labelling and secrets are configured for each of those users. Podman runs containers as child processes of the invoking user, with no setuid binary, and can run them rootless, mapping container root to an unprivileged account. Those are strong foundations, and they hold best when subordinate ID ranges, socket activation, Quadlet unit settings and image trust policy are set deliberately. This guide covers each of those with exact file paths and option names from the Podman project documentation. It is written for Linux, platform and security engineers running Podman on RHEL-family and other distributions, on servers and developer workstations. For runtime security concepts that apply to every engine, see the Container Runtime Security Guide.
This is an independent guide from NHI Mgmt Group, based on the Podman project's public documentation as of November 2026. It is not sponsored or reviewed by the Podman project. Product features change, so confirm settings against the Podman project's current documentation.
Key takeaways
- Run rootless wherever you can. Container root maps to the invoking user, and a rootless container cannot gain more privileges than that user, so a container escape lands in an unprivileged account.
- Subordinate ID ranges are an access control. Entries in
/etc/subuidand/etc/subgidmust not overlap, and must never subordinate another active user's ID, or one user can act as another. - The Podman API socket equals the user's shell. The documentation states the API grants full access to Podman functionality and allows code execution as the service user; keep it on the Unix socket or behind SSH or mutual TLS.
- Keep SELinux labelling on.
label = trueis the default incontainers.conf; relabel volumes with:Zrather than reaching forlabel=disable. - Set an image trust policy. The containers-policy documentation strongly recommends a
defaultofrejectinpolicy.json, with signed images allowed per registry.
How identity and access work in Podman
Podman has no central service that every user talks to. Each podman command runs as the user who typed it, and the containers it starts are that user's processes. Identity on a Podman host therefore comes down to Linux accounts, the user namespace mapping for each account, and the SELinux or AppArmor confinement applied to containers.
Rootful and rootless
| Aspect | Rootful Podman | Rootless Podman |
|---|---|---|
| Runs as | root | An ordinary user |
| Container root maps to | Host root unless --userns is set | The invoking user's UID |
| Image storage | /etc/containers/storage.conf graphroot | ${XDG_DATA_HOME}/containers/storage (rootless ignores system graphroot and runroot) |
| API socket | /run/podman/podman.sock | $XDG_RUNTIME_DIR/podman/podman.sock |
| Registry credentials | Root's auth file | ${XDG_RUNTIME_DIR}/containers/auth.json by default |
| Quadlet units | /etc/containers/systemd/ | ~/.config/containers/systemd/ |
| Typical use | Workloads needing host devices or privileged networking | Most services, CI runners and developer use |
User namespaces and subordinate IDs
Rootless Podman uses user namespaces. The setuid helpers newuidmap and newgidmap set up the mapping from /etc/subuid and /etc/subgid, whose entries take the form USERNAME:UID:RANGE and are added by root with usermod --add-subuids and --add-subgids. Container UID 0 maps to the user; container UID 1 maps to the first subordinate ID. The rootless tutorial makes four points that matter for security:
- No overlap: overlapping ranges let one user enter another user's namespace and corrupt it.
- No active IDs: subordinating an active user's ID to someone else enables impersonation; subordinating a group ID lets the user act as that group inside containers.
- Local files: the files must be local; network-provided sources such as LDAP or Active Directory are not used for these ranges, so manage them with configuration management.
- Apply changes cleanly: after edits, run
podman system migrateas the user to stop containers and the pause process.
Within a host, --userns=keep-id maps the user's own UID into the container so a non-root container user can write to bind mounts. For rootful workloads, consider userns options in containers.conf (default host, alternative private) or a per-container UserNS= in Quadlet so container root is not host root. See the Linux Namespaces and cgroups Security Guide.
Admin roles and privileged access
On a Podman host the privileged roles are Linux roles. Map them explicitly:
- Root and sudoers: can run rootful containers, edit
/etc/containers/*(includingpolicy.json,registries.confandcontainers.conf) and change subordinate ID files. Restrict sudo rules to named commands and review them. See the Red Hat Enterprise Linux Security Hardening Guide. - Service accounts running rootless containers: create a dedicated non-login account per service, give it its own subordinate range, and enable lingering only for accounts that run Quadlet services at boot. See the Linux Service Accounts and Keytab Management Guide.
- Developers: rootless Podman lets them work without sudo. Keep it that way, and avoid adding them to groups that grant access to rootful sockets.
Privilege flags to control
| Setting | Effect per the Podman docs | Guidance |
|---|---|---|
--privileged | Turns off capability dropping, device limits, read-only mount points, AppArmor or SELinux separation and seccomp | Reserve for named exceptions; rootless privileged containers still cannot exceed the user |
--cap-add | Adds to the default set; docs flag CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_MKNOD, CAP_SYS_MODULE as particularly dangerous outside a user namespace | Review each addition; prefer --cap-drop all then add what is needed |
--security-opt label=disable | Turns off SELinux label separation | Use :Z or :z volume options instead |
--security-opt seccomp=unconfined | Removes the seccomp filter | Supply a custom profile with seccomp=profile.json instead |
--security-opt unmask=ALL | Unmasks default-masked paths such as /proc/kcore, /proc/keys and /sys/firmware | Keep defaults; unmask specific paths only when justified |
--env-host | Passes the whole host environment into the container | Avoid; it can carry host secrets |
The default capability set in containers.conf (default_capabilities) is CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID and SYS_CHROOT. See the Linux Capabilities Guide for trimming it further.
Integrations, API tokens and service accounts
The Podman API socket
podman system service serves a Docker-compatible and native REST API. The documentation is direct about the risk: the API grants full access to Podman functionality, allows arbitrary code execution as the user running the service, and offers no way to limit or audit that access. By default, Unix socket file permissions mean only the service's user can connect.
- Prefer socket activation:
podman.socket(system or user unit) starts the service on demand, and the service exits after the inactivity timeout (--time, default 5 seconds;0means never). Keep the timeout short rather than running a permanent listener. - Remote access: forward the Unix socket over SSH, as the docs recommend, and restrict which keys can reach the account.
- TCP: the docs strongly recommend against network exposure without mutual TLS. If TCP is unavoidable, set
--tls-cert,--tls-keyand--tls-client-caso clients must present a certificate from your CA. See the mTLS Guide. - Mounting the socket into containers: any container given the socket can start containers as that user. Treat such tools (CI agents, reverse proxies, dashboards) as holding the user's full privileges, and prefer running them under a dedicated rootless account.
Podman secrets
podman secret create stores secrets outside images: they are not included in podman commit output or podman export archives, and each is limited to 512 kB.
| Driver | Storage per the docs | When to use |
|---|---|---|
file (default) | A read-protected file | Single host with disk encryption and tight account separation |
pass | GPG-encrypted file | Where secrets must be encrypted at rest on the host |
shell | Your scripts for list, lookup, store and delete | Fetch from an enterprise secrets manager at run time |
Consume secrets as files: --secret source=name,type=mount,uid=1001,gid=1001,mode=0400 places them at /run/secrets/name. Set mode explicitly, because mounted secrets default to mode 0444 owned by UID 0. type=env is available but environment variables are easier to leak through process listings and crash dumps. Secrets are written when the container is created, and --replace affects only containers created afterwards, so rotation means recreating containers. Our recommendation: rotate application secrets at least every 90 days and on any staff or host change, and drive rotation through a Quadlet restart. See the Secrets Management Guide.
Registry credentials
podman login writes to the user's auth file, by default under ${XDG_RUNTIME_DIR}/containers/ for rootless users. Use registry robot or service accounts scoped to pull, never a person's credentials, on servers. Podman secrets can also hold registry credentials. See the Container Registry Security Guide.
Logging and detection
- Podman events:
events_loggerdefaults tojournald(alternativesfileornone; never usenonein production). Filter withjournalctl SYSLOG_IDENTIFIER=podmanand thePODMAN_EVENTfield, or streampodman events --format jsonto your collector. - Container logs: with
log_driverempty, Podman uses journald when the journal is available and k8s-file otherwise. Journald gives central forwarding without per-container files. - API activity: the default
podman.socketunits log API calls at info level, which helps reconstruct remote actions. - Host audit: watch
/etc/subuid,/etc/subgid,/etc/containers/and Quadlet directories with auditd or file integrity monitoring. See the Linux Logging and auditd for Defenders Guide.
| Event | Why it matters | ATT&CK |
|---|---|---|
Container exec into a production container | Interactive access outside deployment pipelines | T1609 |
Container create or start with privileged options or host mounts | Possible preparation for host escape | T1610, T1611 |
Image pull from an unexpected registry or pull-error due to policy | Untrusted image or a blocked attempt | T1525 |
secret create or remove outside change windows | Credential tampering | T1552.001 |
| New listener on the Podman API over TCP | Remote control path opened | T1552.007 |
Metrics that show progress: share of containers running rootless; count of containers with --privileged or label=disable (target zero outside approved exceptions); hosts with policy.json default reject; Podman API sockets reachable over TCP (target zero); median age of secrets in use.
Hardening baseline
Host configuration
- SELinux enforcing: keep
label = trueincontainers.conf. Relabel bind mounts with:Zfor a single container or:zwhen several containers share the content; never relabel system directories such as/homeor/usr. See the SELinux Guide. - Seccomp and AppArmor: keep
seccomp_profileat the shipped/usr/share/containers/seccomp.jsonor a tighter profile; on AppArmor systems keepapparmor_profile = "container-default". - Process limits: keep
cgroups = "enabled"and set apids_limit; confirm the current default value in the containers.conf documentation for your release. - Image trust: in
/etc/containers/policy.jsonset"default": [{"type": "reject"}], then allow your registries under thedockertransport withsigstoreSigned(akeyPath, orfulciowithrekorPublicKeyPath) orsignedBy. A user's$HOME/.config/containers/policy.jsontakes precedence for that user, so monitor for it on servers. See the Sigstore Guide.
Quadlet unit baseline
Quadlet turns .container files into systemd services. Quadlet units do not use systemd's User=, Group= or DynamicUser= for rootless operation; place rootless units in the user's search path instead. A hardened [Container] section looks like this:
User=a numeric non-root UID, andUserNS=where rootfulNoNewPrivileges=true(defaultfalse)DropCapability=allplus only theAddCapability=entries the service needsReadOnly=true(defaultfalse), withReadOnlyTmpfs=truefor scratch pathsSecret=for credentials, neverEnvironment=Pull=andAutoUpdate=registryonly for images covered by your signature policy- No
SecurityLabelDisable=, noSeccompProfile=unconfined, andPodmanArgs=only where unavoidable, as the docs advise
Combine this with systemd sandboxing on the unit where compatible. See the systemd Service Hardening Guide.
Rollout order
- Inventory hosts, accounts with subordinate ranges, rootful containers and any enabled
podman.socketor TCP service. - Fix subordinate ID overlaps and remove ranges that cover active IDs; run
podman system migrateper user. - Move rootful services to rootless Quadlet units under dedicated accounts, one service at a time, keeping the old unit disabled for rollback.
- Deploy
policy.jsonin a pilot with signed images, watch forpull-errorevents, then roll out. - Apply the Quadlet baseline and SELinux relabelling, testing each service in staging first.
- Forward Podman events and journald to the SIEM and set alerts for the events above.
Apply Podman security updates from your distribution promptly and watch the Podman security advisories.
Common misconfigurations
- Overlapping or hand-edited subordinate ranges. Users can reach each other's namespaces. Fix: generate ranges centrally and check for overlap.
- A permanent API listener.
--time 0or a TCP endpoint without mutual TLS leaves a code execution path open. Fix: socket activation, SSH forwarding, mTLS. - SELinux turned off to fix a volume error. Fix:
:Zrelabelling or a targeted label type, keeping separation on. - Secrets in environment variables or images. Fix: Podman secrets mounted as files with explicit
mode, or theshelldriver to an enterprise vault. - An accept-anything trust policy. Fix:
defaultrejectwith signed registries allowed. - Privileged containers for convenience. Fix: add specific capabilities or devices and document each exception.
| Control area | NIST SP 800-53 Rev 5 | CIS Controls v8.1 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|
| Rootless operation and capability limits | AC-6, SC-39 | 4 Secure Configuration | A.8.2, A.8.9 |
| API socket protection | AC-3, SC-8 | 4 Secure Configuration, 6 Access Control Management | A.8.5, A.8.20 |
| Secrets and registry credentials | IA-5, SC-28 | 3 Data Protection | A.5.17, A.8.24 |
| Image signature policy | SI-7, SR-11 | 2 Inventory and Control of Software Assets | A.8.19 |
| Events and logs | AU-2, AU-12 | 8 Audit Log Management | A.8.15, A.8.16 |
Practitioner checklist
- Run services rootless under dedicated accounts and use Quadlet units in the user search path.
- Audit
/etc/subuidand/etc/subgidfor overlap and active IDs; manage them centrally. - Keep the Podman API on the Unix socket with socket activation; use SSH or mutual TLS for remote access.
- Keep SELinux enforcing with
label = true; relabel volumes with:Zor:z. - Set
NoNewPrivileges=true,DropCapability=allandReadOnly=truein Quadlet units, adding back only what is needed. - Set
policy.jsondefault torejectand allow signed images from approved registries. - Store credentials as Podman secrets mounted with explicit mode, or via the
shelldriver to your vault. - Forward Podman events and container logs to the SIEM and alert on exec, privileged creates and policy pull errors.
- Track Podman security advisories and your distribution's container-tools updates.
Standards and references
- Podman: podman-run reference
- Podman: Basic Setup and Use of Podman in a Rootless Environment
- Podman: podman-system-service reference
- Podman: podman-systemd.unit (Quadlet) reference
- Podman: podman-secret-create reference
- Containers project: containers.conf reference
- Containers project: containers-policy.json reference
- Podman: podman-events reference
Related NHI Mgmt Group resources: Container Runtime Security Guide · Container Security Guide · SELinux Guide · Container Image Security Guide