| 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 5.6 or later |
| Privilege | sudo for system policy and /etc/pki/containers/ key placement on the lab host |
| Scope | Image signature policy — policy.json scopes and precedence, podman image trust show and set, trust types (accept, reject, signedBy, sigstoreSigned), podman image sign with GPG simple signing, registries.d sigstore paths, pull-time verification including unsigned and wrong-key failures, and Podman 5.8 vs 6 --signature-policy. Does not cover registry login, TLS CA setup, Cosign tutorials, or Kubernetes admission policy. |
| Related guides | Podman login and registry auth Configure a private registry Configure registries.conf Push images to a registry Podman containers.conf |
Before you tune signature policy, separate three layers that often get mixed up on the same podman pull:
Registry authentication → Who are you? → podman login / auth.json
TLS verification → Correct HTTPS host? → CA certificates in certs.d
Image signature policy → Signed by trusted key? → policy.jsonAll three can apply to one pull. Logging in does not replace signature verification, and trusting a registry TLS certificate does not mean every image from that registry is signed the way your policy requires.
policy.json, podman image trust, and pull-time verification with signedBy and sigstoreSigned. It does not cover registry username/password authentication (podman login), TLS CA trust in certs.d, Cosign keyless workflows, or Kubernetes admission controllers that verify signatures at deploy time.
What is policy.json?
When Podman pulls from transports such as docker://, containers/image consults signature policy before accepting content. The system file on this host is:
/etc/containers/policy.jsonRootless sessions may also read:
$HOME/.config/containers/policy.jsondepending on configuration. RHEL ships extra sigstoreSigned rules for Red Hat registries in the system file — do not assume upstream Podman defaults match your distribution copy verbatim.
List the active system policy path:
ls -la /etc/containers/policy.jsonOn this host the file exists and includes a permissive default plus Red Hat registry sigstore rules.
Rootful podman with sudo typically reads /etc/containers/policy.json. Rootless sessions may prefer $HOME/.config/containers/policy.json when that file exists — if pulls behave differently between sudo podman and podman as your user, compare both paths before chasing signature errors.
Inspect the current trust policy
podman image trust show presents policy.json in a table:
podman image trust showSample output on a fresh RHEL 10.2 host (before lab changes):
TRANSPORT NAME TYPE ID STORE
all default accept
repository registry.access.redhat.com sigstoreSigned N/A
repository registry.redhat.io sigstoreSigned N/A
docker-daemon acceptColumns map to transport, scope name, trust type, key identity when applicable, and signature store path.
Read the raw JSON Podman uses:
podman image trust show --rawThe CLI accept type corresponds to insecureAcceptAnything in raw JSON — search for the requirement type, not the literal word accept in the file.
Machine-readable output:
podman image trust show --jsonUse --json when scripts need structured policy data.
Understand trust types
| Podman trust CLI type | Meaning in practice |
|---|---|
accept |
No signature required for this scope |
reject |
Refuse images from this scope |
signedBy |
Require simple-signing GPG signature from configured public key |
sigstoreSigned |
Require sigstore-format signatures per policy key paths |
In raw policy.json, accept appears as:
{ "type": "insecureAcceptAnything" }signedBy uses keyPath pointing at a public key file. sigstoreSigned on RHEL includes keyPath and rekorPublicKeyPath entries for Red Hat registry content.
How policy scope precedence works
Policy matches the most specific scope first, then falls back toward broader rules:
specific image or tag
↓
repository path
↓
registry host
↓
defaultExample scope names you might configure:
registry.example.test:5000/demo/hello
registry.example.test:5000/demo
registry.example.test:5000
defaultIf nothing more specific matches, default applies. A signedBy rule on registry.example.test:5000/demo does not automatically cover registry.example.test:5000/other — scope strings matter when debugging surprise pulls.
When two rules could match, the engine picks the longest matching scope name. That is why a registry-wide accept does not override a repository-level signedBy on the same host — the more specific repository rule wins for images under that path.
Reject a registry scope
Prove enforcement with a lab registry at registry.example.test:5000 (HTTP, marked insecure in registries.conf.d for this lab). After pushing a test image, reject the whole registry:
podman image trust set --type reject registry.example.test:5000Remove the local copy so the next pull must pass policy, not reuse a cached layer:
podman rmi registry.example.test:5000/demo/hello:v1Pull again and confirm policy blocks the registry scope:
podman pull registry.example.test:5000/demo/hello:v1Sample output on Podman 5.8.2:
Trying to pull registry.example.test:5000/demo/hello:v1...
Error: unable to copy from source docker://registry.example.test:5000/demo/hello:v1: Source image rejected: Running image docker://registry.example.test:5000/demo/hello:v1 is rejected by policy.That error comes from policy — not from registry login or TLS alone.
Allow a registry without signatures
Return the lab registry to unsigned acceptance:
podman image trust set --type accept registry.example.test:5000Pull succeeds again:
podman pull registry.example.test:5000/demo/hello:v1Sample output:
Getting image source signatures
Copying blob sha256:ce980a8f5545faa3125a489aad32c00d6cf13d80a302308c3963b524085657af
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destinationaccept means policy does not require a signature at that scope. It does not disable TLS, skip podman login, or imply the registry is secure in every other sense.
Podman 5.8 vs Podman 6 image trust set
On Podman 5.8.2 tested here, podman image trust set updates the normal policy file without an extra flag:
podman image trust set --type reject registry.example.test:5000podman image trust set --help on 5.8.2 lists only --type and --pubkeysfile — no --signature-policy option.
Podman 6+ requires an explicit policy path:
podman image trust set --signature-policy /etc/containers/policy.json --type reject registry.example.test:5000That is a real CLI breaking change. Commands in this article use 5.8 syntax unless noted; add --signature-policy when you move to Podman 6.
Configure signedBy for a repository
Simple signing needs a GPG key pair. This lab uses a disposable key in a dedicated GNUPGHOME — use your organization's key management in production.
Generate a signing key in an isolated keyring (lab only):
export GNUPGHOME=/root/podman-image-signing-lab/gnupg
gpg --batch --passphrase '' --quick-generate-key 'lab@example.test' rsa4096 sign 0List the long key ID you will pass to --sign-by:
gpg --list-secret-keys --keyid-format longExport the public key to the conventional system path:
/etc/pki/containers/lab-signing.pubPodman does not auto-discover every file under /etc/pki/containers/ — policy references the path you pass to --pubkeysfile.
Require signatures for one repository scope:
podman image trust set --type signedBy --pubkeysfile /etc/pki/containers/lab-signing.pub registry.example.test:5000/demopodman image trust show then lists the scope as signed with the key identity.
On Podman 6+, add --signature-policy /etc/containers/policy.json to every podman image trust set command in this section.
Why /etc/pki/containers is recommended
Podman accepts any readable path with --pubkeysfile. /etc/pki/containers/ is the conventional system location on RHEL-family hosts for container signing public keys — it keeps verification material separate from TLS CA bundles and matches paths you will see in distribution documentation.
Podman does not scan that directory automatically. Policy references the exact file you pass to --pubkeysfile or the keyPath entry in raw JSON. Moving a key into /etc/pki/containers/ alone changes nothing until policy points at it.
Configure registries.d storage for GPG simple signatures
registries.conf and registries.d are different families. GPG simple-signing lookaside and sigstore: storage paths live under registries.d — this is not the same as the sigstoreSigned policy requirement, which verifies Sigstore-format signatures through separate policy and attachment configuration.
/etc/containers/registries.d/
$HOME/.config/containers/registries.d/Point the lab registry at a file-based sigstore:
# /etc/containers/registries.d/registry.example.test:5000.yaml
docker:
registry.example.test:5000:
sigstore: file:///var/lib/containers/sigstoreRootful default sigstore also resolves under /var/lib/containers/sigstore; rootless uses ~/.local/share/containers/sigstore when not overridden. The YAML makes the storage location explicit for this registry.
After podman image sign, inspect the sigstore tree to confirm a signature file landed for the digest:
find /var/lib/containers/sigstore -type fPodman stores signatures below the configured sigstore: path in directories derived from the full registry/repository identity and digest — not a shortened repository name alone. If that directory stays empty after signing, policy may still reject pulls because containers/image cannot read a signature for the reference.
Sign an image with podman image sign
Signing needs a registry-reference image — not a bare local ID without transport. On Podman 5.8.2, include the docker:// prefix:
podman image sign --sign-by 4C6B6835E2EE66CD docker://registry.example.test:5000/demo/hello:v1Replace the key ID with your signing subkey from gpg --list-secret-keys --keyid-format long. The command exits silently on success.
Podman stores the signature below the configured sigstore in directories derived from the full registry/repository identity and digest. Inspect the actual path with:
find /var/lib/containers/sigstore -type fVerify a signed image at pull time
The proof is pull-time enforcement — not merely signing a local image you never removed.
Workflow:
- Set
signedBypolicy for the repository scope - Place the public key at the path referenced in policy
- Sign with
podman image sign - Remove the local image
- Pull again
After signing hello:v1, remove the local copy so the pull must verify the signature from sigstore:
podman rmi registry.example.test:5000/demo/hello:v1Pull the same tag and watch for signature verification lines in the transcript:
podman pull registry.example.test:5000/demo/hello:v1Sample output when the signature matches policy:
Getting image source signatures
Checking if image destination supports signatures
Copying blob sha256:ce980a8f5545faa3125a489aad32c00d6cf13d80a302308c3963b524085657af
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
Storing signaturesA successful pull while signedBy is active shows that policy accepted the image. Confirm enforcement by also testing an unsigned image and an image signed by an untrusted key; both should be rejected. The sample lines above are containers/image copy progress messages — they do not by themselves prove cryptographic acceptance by your configured key.
Reject an unsigned image under signedBy
Push another tag under the same registry.example.test:5000/demo scope without signing it, then pull:
podman pull registry.example.test:5000/demo/unsigned:v1Sample output:
Trying to pull registry.example.test:5000/demo/unsigned:v1...
Error: unable to copy from source docker://registry.example.test:5000/demo/unsigned:v1: Source image rejected: A signature was required, but no signature existsSame repository policy, different tag — no signature means rejection.
Reject an image signed with the wrong key
Sign with a second lab key that policy does not trust, then pull:
podman pull registry.example.test:5000/demo/wrongsig:v1Sample output:
Error: unable to copy from source docker://registry.example.test:5000/demo/wrongsig:v1: Source image rejected: None of the signatures were accepted, reasons: Missing key 964D1EBC8024F77DC9362351B466FFE8CA8C8B83Verification checks the key identity — not merely whether any signature blob exists.
Understand sigstoreSigned
sigstoreSigned is separate from simple signedBy. RHEL 10 ships policy for Red Hat registries in the base policy.json:
{
"type": "sigstoreSigned",
"keyPath": "/etc/pki/sigstore/SIGSTORE-redhat-release3",
"rekorPublicKeyPath": "/etc/pki/sigstore/REKOR-signing-key"
}podman image trust show reports these as sigstoreSigned for registry.access.redhat.com and registry.redhat.io. That is distribution-managed sigstore verification — not the GPG simple-signing workflow you configure with --sign-by.
On Podman 5.8.2, podman image trust set --help accepts signedBy, accept, and reject only. Editing raw policy.json (or distribution packages) is how sigstoreSigned entries typically arrive — do not assume every Cosign keyless workflow maps one-to-one without checking your policy schema.
Compare signedBy and sigstoreSigned
signedBy |
sigstoreSigned |
|
|---|---|---|
| Mechanism | GPG simple signing via podman image sign |
Sigstore-format signatures per policy paths |
| Typical setup | Lab or org GPG key in /etc/pki/containers/ |
Vendor keys under /etc/pki/sigstore/ on RHEL |
| CLI trust type | podman image trust set --type signedBy |
Usually preloaded in policy.json |
| This article | Tested end-to-end on private registry | Observed on Red Hat registry scopes |
Set a default reject policy
For a controlled test host, reject everything unless explicitly allowed:
podman image trust set --type reject defaultThen allow specific scopes:
podman image trust set --type accept quay.io
podman image trust set --type signedBy --pubkeysfile /etc/pki/containers/lab-signing.pub registry.example.test:5000/demoResulting table excerpt:
all default reject
repository quay.io accept
repository registry.example.test:5000/demo signed lab@example.testpodman pull quay.io/podman/hello succeeds while scopes without an explicit allow rule hit default reject. Do not apply default reject on a production workstation until every registry you need has a matching allow or signedBy rule — ordinary pulls will fail.
Policy scope example
Layered policy on the lab host:
default → reject
quay.io → accept
registry.example.test:5000/demo → signedBy (lab-signing.pub)Test matrix:
quay.io/podman/hello— pulls without a custom signature (allowed byquay.ioaccept)registry.example.test:5000/demo/hello:v1— pulls only when signed with the trusted keyregistry.example.test:5000/demo/unsigned:v1— rejected (signature required)- Any registry not listed — rejected by
default
That makes precedence tangible: broader registry.example.test:5000 accept rules and narrower …/demo signed rules can coexist — the more specific scope wins, not the broader one. Check podman image trust show for which scope matched.
Never treat podman image sign on a laptop as proof of registry enforcement until you delete the local image and pull from the registry again. The sign → publish signature → remove local copy → pull → verify-or-reject cycle is what production runtimes depend on.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Pull succeeds but you expected rejection | A more-specific accept rule matches, or you are inspecting a different policy file |
Check the exact image scope and active policy.json with podman image trust show --raw |
rejected by policy |
reject on registry or default |
Add accept or signedBy for that scope |
A signature was required, but no signature exists |
signedBy without podman image sign |
Sign the digest, confirm sigstore path in registries.d |
None of the signatures were accepted |
Wrong signing key | Sign with the key whose public half is in policy |
unknown transport on sign |
Missing docker:// prefix |
Use docker://registry/... with podman image sign |
| Rootful policy differs from rootless | Different policy.json paths |
Check /etc/containers/ vs ~/.config/containers/ |
trust set demands --signature-policy |
Podman 6+ | Pass --signature-policy /etc/containers/policy.json |
References
- containers-policy.json(5) — policy schema and scope matching
- podman-image-trust(1) —
showandset - podman-image-sign(1) — simple signing
- containers-registries.d(5) — sigstore and lookaside paths
- Red Hat documentation — Building, running, and managing containers — RHEL signing overview
Summary
Image signature policy answers a different question than podman login or TLS trust: whether signed content from a registry scope is acceptable. policy.json drives that decision; podman image trust show translates it into a readable table and raw JSON.
On Podman 5.8.2, reject and accept scopes produce immediate pull success or failure, and signedBy enforces GPG simple signing when you configure registries.d sigstore paths, sign with podman image sign --sign-by, delete the local image, and pull again. Unsigned and wrong-key pulls fail with distinct errors — proof that verification inspects key identity, not just the presence of a signature file.
sigstoreSigned covers a separate verification path — visible on RHEL for Red Hat registries. Plan for the Podman 6 --signature-policy requirement when you upgrade podman image trust set automation.
When you roll out policy on a shared host, start with podman image trust show on the default file, add narrow signedBy rules for internal registries first, and only then consider default reject once every registry you need has an explicit scope. Registry authentication and TLS setup stay in their own guides; this page owns pull-time signature policy only.

