| 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 installed |
| Privilege | Rootful examples on the lab host; rootless secret paths follow the same commands under the user account |
| Scope | Runtime secrets — podman secret create, ls, inspect, rm, and exists; podman run --secret with type=mount vs type=env, mount targets, and uid/gid/mode; tested --replace behavior; file, pass, and shell drivers; podman build --secret; Quadlet Secret= mount and env forms with rotation via systemctl restart. Does not cover registry login credentials, external vault products, or Kubernetes Secrets. |
| Related guides | Podman login to a registry Run containers with Podman |
Passwords, API tokens, and TLS keys do not belong in image layers or plain docker commit output. A Podman secret stores a small sensitive blob outside the image — passwords, tokens, keys, and similar values up to roughly 512 KB on current Podman releases. Secrets are managed separately from images, are not included in podman commit or podman export, and can be delivered as files or environment variables at runtime or during image build.
This is not a full enterprise secret-management platform. It is Podman's built-in way to keep sensitive values out of images while still feeding them to containers you control.
What is a Podman secret?
Think of a secret as a named blob Podman stores on the host. Typical contents include:
- Database or application passwords
- API tokens and OAuth client secrets
- TLS private keys or client certificates
- SSH private keys
podman secret inspect returns metadata — ID, name, driver, labels, timestamps — and by default does not include the secret value. That property matters when you are proving to yourself (or an auditor) that routine listing and inspection commands do not leak secret material.
Registry login credentials use a different mechanism (podman login and auth files). Runtime secrets are the topic here.
Create a secret from a file
For lab demos, write sample content to a file and create the secret:
printf '%s' 'example-value' > secret.txtRegister it with Podman:
podman secret create app-secret secret.txtSample output (secret ID):
d75332287c9e9dc7f5fb05192Remove the plaintext file when you no longer need it — rm -f secret.txt. On production hosts, creating a temporary file may itself be undesirable; prefer stdin creation in the next section when your shell or orchestrator can pipe the value once.
Create a secret from stdin
Piping avoids leaving a persistent plaintext file on disk:
printf '%s' 'stdin-value' | podman secret create --replace app-secret -The - argument tells Podman to read secret content from standard input. Use a placeholder in documentation and real secret managers in production — do not paste live credentials into shell history.
Create a secret from an environment variable
When the value already exists in your shell session:
export APP_SECRET='env-value'Create the secret from that variable name:
podman secret create --replace --env app-secret APP_SECRETPodman reads APP_SECRET at creation time. It does not mean every future container automatically inherits that host environment variable — delivery still happens through --secret on podman run or Quadlet Secret= lines.
List Podman secrets
List stored secrets:
podman secret lsSample output:
ID NAME DRIVER CREATED UPDATED
d5c69eefefb42d48c83a3b3c1 app-secret file Less than a second ago Less than a second agoColumns map to:
- ID — unique secret identifier
- NAME — friendly name you pass to
--secret - DRIVER — backend (
file,pass,shell, …) - CREATED / UPDATED — timestamps
The secret value never appears in this table.
Inspect a secret
Pull metadata for one secret:
podman secret inspect app-secretSample output (trimmed):
[
{
"ID": "d5c69eefefb42d48c83a3b3c1",
"Spec": {
"Name": "app-secret",
"Driver": {
"Name": "file",
"Options": {
"path": "/var/lib/containers/storage/secrets/filedriver"
}
}
}
}
]Useful fields include ID, name, driver, labels, and driver options. By default, podman secret inspect does not include plaintext secret data. Podman provides --showsecret for explicit retrieval, so do not use that option in routine diagnostics or logged automation.
Mount a secret into a container
The default delivery mode is a file mount:
podman run --rm --secret app-secret registry.access.redhat.com/ubi9/ubi-minimal:latest cat /run/secrets/app-secretSample output:
env-valueDefault behavior:
type=mount
target=/run/secrets/SECRET_NAMEThe secret appears as a file inside the container filesystem.
Change the secret mount target
Set an explicit path with target=:
podman run --rm --secret source=app-secret,target=/run/config/token registry.access.redhat.com/ubi9/ubi-minimal:latest cat /run/config/tokenSample output:
env-valueA fully qualified path such as /run/config/token is used as-is. A relative target such as token is placed under /run/secrets/ on Linux.
Set secret UID, GID, and mode
Mount ownership and permissions apply only to type=mount:
podman run --rm --secret source=app-secret,uid=1001,gid=1001,mode=0440 registry.access.redhat.com/ubi9/ubi-minimal:latest ls -l /run/secrets/app-secretSample output:
-r--r-----. 1 1001 1001 9 Aug 22 23:54 /run/secrets/app-secretThese IDs control how the secret file appears inside the container. They are not the same as ownership of Podman's backend storage on the host.
Expose a secret as an environment variable
Some applications expect configuration through environment variables:
podman run --rm --secret source=app-secret,type=env,target=APP_SECRET registry.access.redhat.com/ubi9/ubi-minimal:latest printenv APP_SECRETSample output:
env-valueEnvironment delivery is convenient but the value can surface through process listings, diagnostics, crash dumps, and accidental logging. Mounted files are often easier to keep out of routine ps and /proc/PID/environ output. Pick the form your application already supports — this guide does not prescribe a universal security winner.
Compare type=mount and type=env
Mount (type=mount) |
Environment (type=env) |
|
|---|---|---|
| Delivery | File under /run/secrets/ (or custom target) |
Named environment variable |
| Ownership | Supports uid, gid, mode |
No mount permission knobs |
| App pattern | Read a file path | Read getenv / os.environ |
| Rotation signal | Replace secret and recreate container to change file content | Replace secret and recreate container to change PID 1 environ |
The rotation row matters — tested behavior is in the next section.
Replace or rotate a secret
Create a secret with initial content:
printf '%s' 'old-mount' | podman secret create --replace rotate-secret ---replace updates an existing secret with the same name instead of failing on duplicate names.
Does --replace update a running container?
Podman documentation is inconsistent here: podman secret create --replace documents that existing containers are not updated, while the runtime --secret documentation says modifications affect secrets inside a container. On the tested Podman 5.8.2 host, the behavior matched the secret create documentation for the original workload process: recreate the container for deterministic rotation. Mount and environment delivery behave differently — and environment secrets have a subtle exec trap.
Test 1 — mounted secret file
Start a long-lived container with a mounted secret:
podman run -d --name rotate-mount --secret rotate-secret registry.access.redhat.com/ubi9/ubi-minimal:latest sleep 600Read the file before rotation:
podman exec rotate-mount cat /run/secrets/rotate-secretSample output:
old-mountReplace the secret:
printf '%s' 'new-mount' | podman secret create --replace rotate-secret -Read the same file in the still-running container:
podman exec rotate-mount cat /run/secrets/rotate-secretSample output on Podman 5.8.2:
old-mountThe mount inside the running container keeps the original content. A new container sees the updated value:
podman run --rm --secret rotate-secret registry.access.redhat.com/ubi9/ubi-minimal:latest cat /run/secrets/rotate-secretSample output:
new-mountTest 2 — environment secret
Create and run with type=env:
printf '%s' 'old-env' | podman secret create --replace rotate-env -Start a long-lived container that reads the secret as an environment variable:
podman run -d --name rotate-env --secret source=rotate-env,type=env,target=ROTATE_ENV registry.access.redhat.com/ubi9/ubi-minimal:latest sleep 600Check PID 1's environment before replace:
podman exec rotate-env sh -c 'tr "\0" "\n" < /proc/1/environ | grep ROTATE_ENV'Sample output:
ROTATE_ENV=old-envReplace the secret:
printf '%s' 'new-env' | podman secret create --replace rotate-env -PID 1 still holds the original value:
podman exec rotate-env sh -c 'tr "\0" "\n" < /proc/1/environ | grep ROTATE_ENV'Sample output:
ROTATE_ENV=old-envA podman exec printenv in the same running container can mislead you — on Podman 5.8.2 it showed new-env here because exec resolves the current secret again:
podman exec rotate-env printenv ROTATE_ENVSample output:
new-envDo not treat podman exec printenv as proof the main workload process received a rotated value. Recreate the container when guaranteed rotation is required.
Remove a Podman secret
Delete a secret by name:
podman secret create temp-secret - <<< 'temp'Remove it when the demo is done:
podman secret rm temp-secretSample output:
454f9a942e6b4db23ece144aaScript existence checks after removal:
podman secret exists temp-secretThe exit status is what scripts should test:
echo exit:$?Sample output after removal:
exit:1On Podman 5.8.2, podman secret rm rotate-secret succeeded even while rotate-mount still had the old file mounted — removing the secret object does not unmount or rewrite files inside running containers. Plan rotation around container recreation, not secret deletion alone.
Secret drivers
Podman selects a backend through containers.conf and per-command --driver. The default is file.
| Driver | Backend |
|---|---|
file |
Podman local protected storage (default) |
pass |
pass GPG-encrypted store |
shell |
Administrator-provided list/lookup/store/delete scripts |
Driver configuration defaults live in Podman containers.conf.
file secret driver
Explicit file driver (same as default):
printf '%s' 'file-driver' | podman secret create --driver=file --replace file-secret -Podman stores the blob in its local secret storage. Do not edit files under the driver path by hand — use podman secret commands.
pass secret driver
The pass driver delegates storage to a GPG-encrypted password store. Prerequisites include the pass utility and a configured GPG key. The pass package was not installed on this RHEL 10.2 lab image, so the command below is the shape to use once pass is available:
podman secret create --driver=pass app-secret secret-input.txtThis is not a GPG or pass initialization guide — install pass, initialize the store, then point Podman at the driver.
shell secret driver
The shell driver calls your scripts for list, lookup, store, and delete operations. Configure it through containers.conf — for example with CONTAINERS_CONF_OVERRIDE during testing:
[secrets]
driver = "shell"
[secrets.opts]
list = "/root/podman-secrets-lab/shell-driver/list"
lookup = "/root/podman-secrets-lab/shell-driver/lookup"
store = "/root/podman-secrets-lab/shell-driver/store"
delete = "/root/podman-secrets-lab/shell-driver/delete"The store helper receives the secret ID in the SECRET_ID environment variable and reads secret bytes from stdin:
#!/bin/bash
set -euo pipefail
id="${SECRET_ID:?missing SECRET_ID}"
cat > "/var/lib/podman-shell-secrets/${id}"
chmod 600 "/var/lib/podman-shell-secrets/${id}"With the override file exported, create and mount a shell-backed secret:
export CONTAINERS_CONF_OVERRIDE=/tmp/shell-secrets.conf
printf '%s' 'shell-backend-value' | podman secret create shell-secret -Mount it in a container:
podman run --rm --secret shell-secret registry.access.redhat.com/ubi9/ubi-minimal:latest cat /run/secrets/shell-secretSample output:
shell-backend-valueShell driver security depends entirely on your scripts and backing store — customizable does not mean automatically safe.
Use secrets during podman build
Build secrets are a separate workflow from runtime podman run --secret. They make a file available only to a specific RUN step.
Create a build context directory and host token file:
mkdir -p buildctx
printf '%s' 'build-token-value' > token.txtContainerfile (buildctx/Containerfile):
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
RUN --mount=type=secret,id=api-token \
test -s /run/secrets/api-token && echo "built-with-secret" > /built-markerBuild with the secret mount:
podman build --secret id=api-token,src=token.txt -t secret-build-demo buildctx/Verify the marker without persisting the token in a layer:
podman run --rm secret-build-demo cat /built-markerSample output:
built-with-secretDo not COPY the secret file or redirect secret content to a path that survives in the final image — that defeats the purpose. Deeper build patterns live in Build images with Podman.
Build secret and layer cache gotcha
Changing only the secret file does not necessarily invalidate a cached RUN layer. Swap the token file and rebuild without other changes:
printf '%s' 'token-v2' > token.txt
podman build --secret id=api-token,src=token.txt -t secret-build-demo buildctx/ 2>&1 | tail -3On this host Podman reused the same cached layer ID:
--> Using cache 9d7165919dbf5322abfa85fcfd4ee3134ed4eada4be7943879f50dc87f8aba30
Successfully tagged localhost/secret-build-demo:latestIf build output genuinely depends on secret content, add a cache-busting build argument, change a line above the secret-consuming RUN, or use --no-cache when you must force a rebuild.
Use secrets with Quadlet
Quadlet .container units accept a Secret= directive in the [Container] section. Create the secret first:
printf '%s' 'quadlet-secret-value' | podman secret create --replace quadlet-secret -Mount form:
[Unit]
Description=Podman secret demo container
[Container]
Image=registry.access.redhat.com/ubi9/ubi-minimal:latest
ContainerName=secret-demo
Secret=quadlet-secret,type=mount,target=/run/secrets/app
Exec=sleep infinity
[Service]
Restart=alwaysSave as /etc/containers/systemd/secret-demo.container, reload, and start:
systemctl daemon-reload
systemctl start secret-demo.serviceRead the mounted file:
podman exec secret-demo cat /run/secrets/appSample output:
quadlet-secret-valueEnvironment form (valid on Podman 5.8.2):
Secret=quadlet-secret,type=env,target=QUADLET_SECRETAfter switching the unit to the env form and restarting:
systemctl restart secret-demo.service
podman exec secret-demo printenv QUADLET_SECRETSample output:
quadlet-secret-valueSee Podman Quadlet container file for general [Container] directive syntax.
Rotate a secret used by Quadlet
A deterministic rotation workflow avoids relying on live-container secret propagation:
- Replace the secret —
printf '%s' 'quadlet-rotated' | podman secret create --replace quadlet-secret - - Restart the generated service —
systemctl restart secret-demo.service - Confirm a new container ID —
podman ps --filter name=secret-demo - Verify consumption —
podman exec secret-demo printenv QUADLET_SECRET(env form) orcatthe mount path
On this host, restart changed the container ID from 61ab6cd7e7c9 to a2618240edfc and the workload saw quadlet-rotated. That is the pattern to document in runbooks — replace, restart, verify — rather than assuming an already-running container picks up the new blob.
Podman secrets vs environment files
| Podman secret | Environment file (--env-file) |
|
|---|---|---|
| Storage | Managed secret object | Plain host file |
| Inspection | Metadata only by default; --showsecret can reveal value |
File contains readable key=value lines |
| Delivery | Mount or env via --secret |
Env vars from file |
| Drivers | file, pass, shell |
No secret backend |
| Build integration | podman build --secret |
Not a build-secret mechanism |
Environment files remain appropriate for non-sensitive configuration. Secrets fit values that should not live in images or casual file reads.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
secret name already exists |
Duplicate name without --replace |
Add --replace or choose a new name |
| Mount path empty in container | Wrong secret name or missing --secret |
Match source= to podman secret ls name |
unknown flag: --module style error for secrets |
Flag after subcommand | Keep --secret on podman run (not a global flag issue for secrets) |
| Rotated secret but app still shows old value | Running container not recreated | Restart workload or systemctl restart Quadlet service |
podman exec printenv shows new value but app does not |
Exec re-resolves secrets; PID 1 does not | Check /proc/1/environ or recreate container |
| Build step ignores new secret content | Layer cache reused | Bust cache or rebuild with --no-cache |
Shell driver no such secret |
Script protocol mismatch | Shell driver passes SECRET_ID in the environment — not $1 |
References
- podman-secret(1) — create, list, inspect, remove
- podman-run(1) — secret option — mount and env delivery
- containers.conf(5) — secrets table — default driver and shell opts
- podman-container.unit(5) — Quadlet
Secret=directive - Red Hat documentation — Using secrets — RHEL overview
Summary
Podman secrets keep sensitive blobs out of images while still feeding them to containers. Create secrets from files, stdin, or host environment variables; list and inspect metadata by default without revealing values; deliver values as mounted files (with optional uid, gid, and mode) or environment variables.
The rotation story is where documentation often disagrees with intuition. podman secret create --replace says existing containers are not updated, while runtime --secret docs suggest otherwise. On Podman 5.8.2, --replace did not change a mounted secret file inside a running container, and environment secrets kept the original value in PID 1's environ even though podman exec printenv could show the updated secret. New containers and restarted Quadlet services picked up rotated values — plan on recreate or systemctl restart when rotation must be guaranteed.
Build secrets mount into individual RUN steps; cache can hide secret changes unless you bust the layer. The file driver covers most hosts; pass and shell backends integrate external stores when you need them. Registry authentication stays in login and registries.conf territory — runtime secrets solve a different problem.

