Container signing is no longer a "nice to have" you can table for the next quarter. A CISA binding operational directive, a dozen customer questionnaires, and the EU CRA's integrity requirements have converged on the same answer: sign every image, verify every pull, and keep the keys out of your CI runners.
Cosign has won the container-signing space — partly because it integrates with Sigstore's keyless flow, partly because it is the reference implementation for OCI 1.1 signatures. This post is the production setup I deploy for engineering teams shipping multiple images per day.
Should You Use Keyless or Key-Based Signing?
Use keyless signing for CI-built artifacts and reserve long-lived keys for air-gapped or high-assurance workflows. Keyless signing binds a signature to an OIDC identity via Fulcio's short-lived certificate, which means there is no signing key for an attacker to steal and no key rotation to manage. The signature proves "this identity signed this image at this time," and the Rekor transparency log proves you cannot retroactively alter the record. For almost every modern pipeline, that is the right tradeoff.
The exceptions are narrow: air-gapped build environments that cannot reach Fulcio, compliance regimes that require a specific HSM-backed key, or offline verification scenarios where the verifier has no network access.
What Are You Actually Signing?
You are signing the image digest — a SHA-256 content address that uniquely identifies the image bytes. Signing a tag is meaningless because tags are mutable. Cosign enforces this: every cosign sign call ultimately resolves to a digest before it signs. Every downstream tool that verifies signatures — admission controllers, policy engines, cosign verify — should do its verification against a digest-pinned reference.
Step-by-Step Implementation
Step 1: Enable OIDC in Your CI
For GitHub Actions, enable id-token: write at the job level. For GitLab, use its native OIDC JWT. For Buildkite, configure the OIDC plugin. Whichever CI you use, the goal is the same: a short-lived OIDC token that Fulcio can exchange for a signing certificate. Never use long-lived credentials for this.
Step 2: Sign the Image From the CI Job
Here is a minimum GitHub Actions job that signs the image and pushes the signature as an OCI referrer:
name: release
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write
packages: write
jobs:
sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3.9.0
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
- name: Sign image with keyless OIDC
env:
COSIGN_YES: "true"
run: |
cosign sign \
--oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/${{ github.repository }}@${{ steps.push.outputs.digest }}
COSIGN_YES=true suppresses the interactive "do you want to write to Rekor?" prompt. In CI you always want yes.
Step 3: Attach a Build Provenance Attestation
A signature alone proves someone signed the image. An attestation proves something about the image. At minimum, attach a SLSA provenance attestation describing how the image was built:
- name: Attest provenance
env:
COSIGN_YES: "true"
run: |
cosign attest \
--predicate provenance.json \
--type slsaprovenance \
--oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/${{ github.repository }}@${{ steps.push.outputs.digest }}
You can generate the provenance JSON with slsa-github-generator or the actions/attest-build-provenance action. Most teams use the latter because it matches GitHub's native attestation UX.
Step 4: Verify Before Every Pull
The signature is worthless if nothing verifies it. Do verification in two places: in CD pipelines before promoting an image between environments, and at the Kubernetes admission boundary. Here is the CD-side verification:
cosign verify \
--certificate-identity-regexp='^https://github.com/acme/.+' \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/acme/widget@sha256:abc123...
Note the --certificate-identity-regexp — this is what prevents an attacker with a GitHub OIDC token for an unrelated repo from forging a signature that passes verification.
Step 5: Enforce Signatures at Kubernetes Admission
Use Kyverno or the Sigstore policy controller to block unsigned images at admission. A minimal Kyverno policy:
apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
name: require-cosign-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-signature
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences:
- "ghcr.io/acme/*"
attestors:
- entries:
- keyless:
subjectRegExp: "^https://github.com/acme/.+"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
Deploy this to every cluster, not just production. The first time you discover an unsigned image is in a dev cluster, not during a prod outage.
Step 6: Pin the Cosign Root of Trust
Sigstore's public good instance uses a TUF root of trust that rotates periodically. Pin the root version in your verification config so you know what you are trusting:
env:
TUF_ROOT: /etc/sigstore/tuf
SIGSTORE_ROOT_FILE: /etc/sigstore/root.json
For highly regulated environments, run your own private Sigstore deployment (Fulcio + Rekor + TUF) inside your network and configure Cosign to use it. This is the supply-chain equivalent of running your own certificate authority — more work, but more control.
What Happens When Rekor Is Unavailable?
Cosign will fail closed by default when it cannot reach Rekor during signing. During verification, it will fail closed when it cannot fetch the transparency log inclusion proof. For CI this is usually the right behavior — a build that cannot be entered in the transparency log should not ship. For admission controllers, configure a degraded mode that allows pulls when Rekor is transiently down but logs loudly and alerts the on-call.
How Do You Handle Base-Image Signatures?
Verify the signatures of the base images you pull before building on top of them. Most official images from Docker Hub and registries like cgr.dev (Chainguard) publish Cosign signatures. Add a verification step to your Dockerfile build pipeline that refuses to build if the base image signature does not match an allowlist of identities. This cuts off a very common supply-chain path — compromised or typosquatted base images.
What About Multi-Arch and Manifests?
Sign the manifest list, not each architecture separately. A multi-arch image has a top-level index that references per-architecture manifests. cosign sign against the index signs the index digest, which transitively covers all architectures. If you sign individual arch manifests, admission controllers will sometimes only pull the arch-specific digest and miss the signature.
How Do You Rotate If a Signing Identity Is Compromised?
With keyless signing there is no signing key to rotate, but you may need to invalidate historical signatures tied to a compromised OIDC identity. The practical flow is to update your verification policies to narrow the allowed identity subjects, re-sign affected images from a new identity, and publish a VEX statement or advisory if the old signatures are on images in the field. Document this runbook before you need it.
How Safeguard.sh Helps
Safeguard.sh treats signatures and attestations as first-class supply-chain signals, not audit checkboxes. Our reachability engine cross-references the signed artifact's SBOM with 100-level depth call graphs, so a signed image that contains a reachable CVE still surfaces as risk — signed does not mean safe. Griffin AI correlates Rekor entries against CI telemetry to flag drift between what was built and what was signed. TPRM ingests third-party vendor attestations and tracks their identity pinning, and our container self-healing runtime rebuilds, re-signs, and re-attests images the moment a reachable CVE is disclosed, keeping your admission policies green without human intervention.