You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
File secrets use bind mounts (breaks under enforcing SELinux); native podman secrets fix it but change update semantics — guidance on preferred approach? #1551
File-based secrets (secrets.<name>.file:) are implemented as plain read-only bind mounts rather than as native podman secrets. On enforcing-SELinux systems this makes the mounted secret unreadable by the container (the long-standing failure in #655), and it also means uid/gid/mode are silently ignored.
The obvious fix — registering file secrets as native podman secrets (the same way environment: secrets are already handled) — resolves both problems cleanly, but changes update semantics (native secrets are copied at container creation, not live-mounted). That is a real behavior change for live-testing / secret-rotation workflows.
Because there is no single mechanism that wins on every axis, I'd like maintainer guidance on the preferred route (in particular, the default) before opening a PR. I have a working implementation and am happy to shape it either way.
This is a focused re-scoping of the still-open portion of #655.
Environment
podman-compose: main (1.6.0, commit e3df104)
podman: 5.8.4
python: 3.14.7
OS: Fedora Linux 44 (Workstation Edition)
SELinux: Enforcing
Current behavior
For a file: secret, get_secret_args() does not create a podman secret. It rewrites the secret into a bind mount:
# get_secret_args(), run path (podman_compose.py:919 on main)volume_ref= ["--volume", f"{source_file}:{dest_file}:{mount_options}"]
A bind mount preserves the host file's SELinux label. A confined container runs as container_t and is denied read on a file labeled e.g. var_t / unconfined_u:object_r:.... This is inherent to bind-mounting an unlabeled host file into a confined container — not a user misconfiguration.
The same design choice is why uid/gid/mode cannot be honored; the code only emits a warning:
Note: the environment: half of #655 is already fixed
The unparsable secret / not supported for runtime secrets tracebacks in #655 came from environment:-sourced secrets, and those are resolved on main: create_secrets_from_environment() (podman_compose.py:802, invoked from compose_up at :4179) runs podman secret create --env and references the secret with --secret. Users still hitting that are on an older release (e.g. Fedora's 1.5.0). Only the file: path remains broken.
Why the existing x-podman.relabel: z/Z is a band-aid
The x-podman.relabel option (podman_compose.py:911) fixes access by appending :z/:Z to the bind mount, which makes podman chcon the original host file:
It mutates the SELinux label of the source file (e.g. /srv/app/pw) in place — a side effect on a file podman doesn't own.
:Z (private) then locks any other service/container out of a shared secret file.
It's a podman-compose-only extension, so the compose file is no longer portable to docker.
It still doesn't address uid/gid/mode.
Candidate fix: native podman secrets
Treat file: secrets like environment: secrets already are — create a real podman secret and reference it:
Podman then copies the secret into the container with the container's own SELinux label (no host relabeling), honors uid/gid/mode, and supports absolute target= paths. This fixes SELinux and the ownership fields out of the box.
The catch: copy vs. live semantics
Native mount secrets are copied at container creation, not live-mounted. I verified that rotating a secret does not reach a running container — and podman restart is not enough; only a full recreate picks it up:
$ echo v1 > s; podman secret create stest s
$ podman run -d --name c --secret stest,type=mount busybox sleep 300
$ podman exec c cat /run/secrets/stestv1
# rotate the secret on the host
$ echo v2 > s; podman secret rm stest && podman secret create stest s
$ podman exec c cat /run/secrets/stest # running containerv1 # <-- NOT updated
$ podman restart c && podman exec c cat /run/secrets/stestv1 # <-- restart is NOT enough
# only `podman-compose up --force-recreate` (rm+create) picks up v2
Today's bind-mount behavior — and docker-compose's — propagates in-place edits live, which matters for dev/live-testing loops and for secret rotation. A straight swap to native secrets fixes SELinux but regresses that.
I also confirmed there is no third mechanism that gives live updates and correct SELinux without touching the host file: a per-mount context= SELinux option is not accepted for single-file bind mounts in podman's -v syntax.
I'd like your call on the design before I open a PR. Options as I see them:
Bind-mount default, native opt-in. Keep current bind-mount behavior as the default (no regression, preserves live edits + docker parity); add a per-secret opt-in (e.g. an x-podman.* field) to use native secrets for SELinux/uid/gid/mode correctness. Downside: Secrets aren't getting created properly when specified in the compose file #655 stays broken by default on enforcing SELinux.
Native default, bind opt-out. Make native secrets the default so Secrets aren't getting created properly when specified in the compose file #655 is fixed out of the box on enforcing SELinux (podman's primary audience); add a per-secret opt-out to bind-mount for live-update workflows. Downside: regresses live updates by default and diverges from docker.
Native only. Always use native secrets. Simplest, always correct on SELinux/ownership; no way to get live-update semantics.
Something else — e.g. auto-detect enforcing SELinux, align with the compose-spec driver/driver_opts fields, or a global config toggle rather than per-secret.
Open questions that may steer the choice:
How much weight should strict docker-compose parity carry here vs. correct-by-default on podman's primary (enforcing-SELinux) platform?
Is a per-secret extension acceptable, or is a single global/project-level toggle preferred?
Should the created secrets be cleaned up on down (labeled by project), matching or diverging from how environment: secrets currently persist?
Happy to implement whichever direction you prefer — I already have the native-secret path working with unit + integration coverage.
Summary
File-based secrets (
secrets.<name>.file:) are implemented as plain read-only bind mounts rather than as native podman secrets. On enforcing-SELinux systems this makes the mounted secret unreadable by the container (the long-standing failure in #655), and it also meansuid/gid/modeare silently ignored.The obvious fix — registering file secrets as native podman secrets (the same way
environment:secrets are already handled) — resolves both problems cleanly, but changes update semantics (native secrets are copied at container creation, not live-mounted). That is a real behavior change for live-testing / secret-rotation workflows.Because there is no single mechanism that wins on every axis, I'd like maintainer guidance on the preferred route (in particular, the default) before opening a PR. I have a working implementation and am happy to shape it either way.
This is a focused re-scoping of the still-open portion of #655.
Environment
Current behavior
For a
file:secret,get_secret_args()does not create a podman secret. It rewrites the secret into a bind mount:https://github.com/containers/podman-compose/blob/e3df104/podman_compose.py#L919
A bind mount preserves the host file's SELinux label. A confined container runs as
container_tand is deniedreadon a file labeled e.g.var_t/unconfined_u:object_r:.... This is inherent to bind-mounting an unlabeled host file into a confined container — not a user misconfiguration.The same design choice is why
uid/gid/modecannot be honored; the code only emits a warning:https://github.com/containers/podman-compose/blob/e3df104/podman_compose.py#L921-L928
Reproduction
compose.yaml:Minimal confirmation that the bind mount itself is what SELinux blocks (no podman-compose involved):
The matching AVC (as reported by multiple users in #655):
Note: the
environment:half of #655 is already fixedThe
unparsable secret/not supported for runtime secretstracebacks in #655 came fromenvironment:-sourced secrets, and those are resolved onmain:create_secrets_from_environment()(podman_compose.py:802, invoked fromcompose_upat :4179) runspodman secret create --envand references the secret with--secret. Users still hitting that are on an older release (e.g. Fedora's 1.5.0). Only thefile:path remains broken.Why the existing
x-podman.relabel: z/Zis a band-aidThe
x-podman.relabeloption (podman_compose.py:911) fixes access by appending:z/:Zto the bind mount, which makes podmanchconthe original host file:/srv/app/pw) in place — a side effect on a file podman doesn't own.:Z(private) then locks any other service/container out of a shared secret file.uid/gid/mode.Candidate fix: native podman secrets
Treat
file:secrets likeenvironment:secrets already are — create a real podman secret and reference it:Podman then copies the secret into the container with the container's own SELinux label (no host relabeling), honors
uid/gid/mode, and supports absolutetarget=paths. This fixes SELinux and the ownership fields out of the box.The catch: copy vs. live semantics
Native mount secrets are copied at container creation, not live-mounted. I verified that rotating a secret does not reach a running container — and
podman restartis not enough; only a full recreate picks it up:Today's bind-mount behavior — and docker-compose's — propagates in-place edits live, which matters for dev/live-testing loops and for secret rotation. A straight swap to native secrets fixes SELinux but regresses that.
I also confirmed there is no third mechanism that gives live updates and correct SELinux without touching the host file: a per-mount
context=SELinux option is not accepted for single-file bind mounts in podman's-vsyntax.Tradeoff matrix
uid/gid/mode:z/:Z(x-podman.relabel)Question for maintainers — preferred route?
I'd like your call on the design before I open a PR. Options as I see them:
x-podman.*field) to use native secrets for SELinux/uid/gid/modecorrectness. Downside: Secrets aren't getting created properly when specified in the compose file #655 stays broken by default on enforcing SELinux.driver/driver_optsfields, or a global config toggle rather than per-secret.Open questions that may steer the choice:
down(labeled by project), matching or diverging from howenvironment:secrets currently persist?Happy to implement whichever direction you prefer — I already have the native-secret path working with unit + integration coverage.