Back

Container Image Signing Explained: How It Works and How to Sign Images with Cosign

Container Image Signing Explained: How It Works and How to Sign Images with Cosign

You can use a signed container image to verify who signed it and what it contained as it moves through registry copies and cluster pulls. The Kubernetes project signs its own release images with keyless Cosign, and the same tooling fits your team’s pipeline.

This guide covers how signatures bind to image digests in Open Container Initiative (OCI) registries, how key-based and keyless Cosign signing differ, and how Kubernetes enforces them.

What Is Container Image Signing?

Container image signing produces a cryptographic signature over an image’s digest. Anyone who pulls the image can then confirm who signed it and that nothing has changed since. A digest is a cryptographic hash of the content (OCI distribution spec), and one manifest digest can sit behind many mutable tags. Cosign signs over the manifest’s Secure Hash Algorithm 256-bit (SHA-256) hash, the algorithm the OCI image specification requires every implementation to support.

Cosign writes the signature to the registry as a separate OCI object that references the digest, and a signing key or an OpenID Connect (OIDC) identity proves who produced it. That separation lets you sign an image already in the registry and attach several signatures to one digest.

How Container Image Signing Works

The signer first gets the digest, which a registry returns in the Docker-Content-Digest header. A private key or a short-lived keyless certificate then signs the payload that wraps that digest. Cosign pushes the signature to the same repository as the image, and a downstream verifier checks it against a public key or a transparency log entry before the container runs.

Cosign’s original scheme stores the signature under a sha256-<digest>.sig tag as an OCI image manifest, and the registry keeps that tag even after the image is deleted because the link back to the image is only a naming convention.

OCI 1.1 added a subject field on manifests and a referrers endpoint, and current Cosign stores signatures using the OCI 1.1 referrers specification. For keyless signatures, Rekor, Sigstore’s transparency log, persists the digest, signature, and certificate in an append-only ledger.

Why Container Image Signing Matters

Malicious images that impersonate common tools on Docker Hub have drawn millions of pulls. A signature bound to a digest lets a cluster reject such an image before a container starts. The SolarWinds compromise involved a trojanized Orion DLL, or dynamic-link library (DLL), shipped through a legitimate installer and signed with a SolarWinds certificate, which is why provenance has to be tied to the build itself.

Supply-chain frameworks call for signing and verification across an artifact’s life cycle. The Supply-chain Levels for Software Artifacts (SLSA) framework’s SLSA Build L2 requires provenance tied to the build infrastructure through a digital signature, while higher SLSA requirements keep signing keys out of reach of the environment running user-defined build steps.

Container Image Signing Tools: Cosign, Notation, and Docker Content Trust

Teams often evaluate Cosign, Notation, Docker Content Trust (DCT), and Amazon Web Services (AWS) Signer. Notation and AWS Signer share a plugin path, since Amazon Elastic Container Registry (ECR) manual signing runs the Notation command-line interface (CLI) with the Signer plugin. DCT appears because existing pipelines still use it, though Docker is retiring it, with a full shutdown of notary.docker.io on December 8, 2026.

The table compares each tool’s signing model, registry support, and keyless option.

ToolBacked BySigning ModelRegistry SupportKeyless Option
Cosign (Sigstore)Open Source Security Foundation (OpenSSF) project backing, under the Linux FoundationOCI artifact attached to the image digestBroad OCI registry support; tested on ECR, Google Artifact Registry, Docker Hub, Azure Container Registry (ACR), and HarborYes (OIDC token, Fulcio, and Rekor)
Notation (Notary v2)Notary Project, CNCF Incubating; contributors include Microsoft, AWS, and DockerJSON Web Signature (JWS) envelope by default, or a Concise Binary Object Representation (CBOR) Object Signing and Encryption (COSE) envelope, stored as an OCI artifactOCI-compliant registries, with keys from plugins such as AWS Signer or Azure Key VaultNo (keys via plugins such as Azure Key Vault or AWS Signer)
Docker Content Trust (DCT)Docker (being retired, with notary.docker.io shutting down December 8, 2026), built on The Update Framework (TUF) and Notary v1Notary v1 digital signaturesDocker registries via notary.docker.ioNot applicable (being retired)
Amazon Web Services (AWS) SignerAWS managed signing serviceManaged signing on push, or Notation CLI plus Signer pluginECR for managed signing, and any OCI 1.0 compatible registry through the Notation CLINo; Signer manages keys and certificates

You should start with Cosign because the Kubernetes project signs its own release images with it and its tested registry list covers ECR, Google Artifact Registry, Docker Hub, ACR, Harbor, and Artifactory.

Key-Based vs. Keyless Signing

Key-based signing puts the full key lifecycle in your hands along with its obligations. The –key flag accepts a local key pair or a key management service reference (KMS) such as awskms://, gcpkms://, azurekms://, and hashivault://, which keeps the private key inside the KMS throughout signing. You control rotation, access, and revocation, which fits air-gapped environments. Private Sigstore deployments cover the same scenarios if you prefer that approach.

Keyless signing removes key custody, as long as your verification policy pins the expected issuer and workflow identity. Cosign exchanges an OIDC token for a certificate that Fulcio, Sigstore’s certificate authority, issues with a 10-minute validity, and Cosign destroys the ephemeral private key after signing. A verifier then checks the Rekor entry instead of a long-lived public key. In exchange, the signer’s identity, via email or a continuous integration (CI) workflow path, lands in a public certificate transparency log, and Cosign needs network access to Fulcio and Rekor at signing time.

How to Sign a Container Image with Cosign

The signing path starts with installing Cosign and ends when you sign an immutable digest reference.

1. Install Cosign

You need Docker or another image builder, push access to an OCI registry, and a current Cosign. The project supports the latest release and the last v2 release, v2.6.5. The installation docs cover Homebrew and go install alongside release binaries and distro packages.

brew install cosign
# or
go install github.com/sigstore/cosign/v3/cmd/cosign@latest

Either command installs the Cosign CLI for the next steps.

2. Generate a Key Pair (Key-Based Path)

Skip this step if you’re going keyless.

cosign generate-key-pair

Cosign prompts for a password and writes cosign.key and cosign.pub, and COSIGN_PASSWORD supplies the password non-interactively.

3. Build, Push, and Capture the Digest

ECR requires a token from aws ecr get-login-password before the push, and Google Artifact Registry uses gcloud auth configure-docker <region>-docker.pkg.dev. The first line names the ECR image, and the last line reads the pushed digest back.

IMAGE_URI=<aws_account_id>.dkr.ecr.<region>.amazonaws.com/<repository>:<tag>
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com
docker build -t "$IMAGE_URI" .
docker push "$IMAGE_URI"
IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_URI")

IMAGE_DIGEST now holds <registry>/<image>@sha256:<digest>, the form you should sign.

4. Sign by Digest (Key-Based)

Pass your private key and the digest reference.

cosign sign --key cosign.key "$IMAGE_DIGEST"

Cosign prompts for the key password and pushes the signature next to the image. Signing a tag instead prints a warning because tags are mutable. For a multi-platform image, the –recursive flag tells Cosign to sign each platform image alongside the index.

5. Sign Keyless (CI Path)

Drop –key and Cosign requests a Fulcio certificate using the OIDC token in the environment.

cosign sign --yes "$IMAGE_DIGEST"

–yes skips the confirmation prompts, which is essential in CI.

What You Built

You now have a registry-hosted image signed against its immutable digest with either a private key or a keyless OIDC identity. The next step is to verify that signature and enforce the same identity requirements before Kubernetes starts the image.

Verifying Signed Images

For a key pair, cosign verify –key cosign.pub <registry>/<image>@sha256:<digest> verifies claims and signature against your public key.

Keyless verification in Cosign 2.0 and later requires two flags, –certificate-identity (or –certificate-identity-regexp for a pattern) and –certificate-oidc-issuer. The full command is cosign verify –certificate-identity=<identity> –certificate-oidc-issuer=<issuer> <registry>/<image>@sha256:<digest>.

The Kubernetes project verifies its own control plane images with the identity [email protected] and the issuer https://accounts.google.com, a pair you can run as a first test. A successful key-based run prints a payload like this one from the verification docs:

[{"critical":{"identity":{"docker-reference":"gcr.io/user-vmtest2/demo"},"image":{"docker-manifest-digest":"sha256:410a07f17151ffffb513f942a01748dfdb921de915ea6427d61d60b0357c1dcd"},"type":"cosign container image signature"},"optional":null}]

Cosign has already matched docker-manifest-digest against the image you named. When verification fails with no matching signatures: none of the expected identities matched what was in the certificate, the image carries a signature from an identity your flags don’t allow. A no signatures found for image error means no one has signed that digest.

Enforcing Image Signing Policies in Kubernetes

In Kubernetes, an admission controller refuses images that fail signature checks. Sigstore Policy Controller validates signatures through a ClusterImagePolicy whose authorities hold a public key in key.data or a keyless.identities list of issuer and subject. It only inspects namespaces labeled policy.sigstore.dev/include: “true”, rejects images that match no policy by default, and offers mode: warn for a staged rollout.

Kyverno’s ImageValidatingPolicy uses cosign attestors and a validationActions switch between Audit and Deny. Kyverno deprecated the ClusterPolicy and Policy types, including verifyImages rules, in the v1.19 release and plans to remove them in 1.20. Open Policy Agent (OPA) Gatekeeper relies on external data providers such as Ratify, and Ratify paired with Gatekeeper is how you verify Notation signatures, including the ones AWS Signer creates, before Kubernetes admits a pod.

Admission control doesn’t guarantee node-level enforcement. The check runs in one cluster component, and a compromised controller leaves every node pulling images with no signature check of its own. CRI-O, a Container Runtime Interface (CRI) runtime, closes that gap from v1.28 with a policy.json whose sigstoreSigned entries name a fulcio block (oidcIssuer, subjectEmail, caData) and rekorPublicKeyData.

The fulcio block requires an email subject, which fits keyless signatures from a person or a service account email. For signatures from a CI workflow identity, rely on the admission controller, or sign with a key and put its public key in the policy. A “default”: [{“type”:”reject”}] rule keeps anything unmatched from starting.

You should run policies in audit or warn mode and review what they reject before you switch to enforce.

Integrating Image Signing into CI/CD Pipelines

Signing belongs in the continuous integration and continuous delivery (CI/CD) pipeline where the digest first exists. The pipeline should preserve the digest returned by the push step through signing, verification, and promotion.

Tags can change between two steps in the same workflow, and the digest has to come from the push step itself. A GitHub Actions job grants id-token: write so GitHub mints the OIDC token, then installs Cosign with sigstore/[email protected] (installer v4 is required for Cosign v3). The signing step uses the digest output of docker/build-push-action to sign ${tag}@${DIGEST}. The snippet below condenses the installer’s own example:

      - env:
          DIGEST: ${{ steps.build-and-push.outputs.digest }}
          TAGS: ${{ steps.docker_meta.outputs.tags }}
        run: |
          images=""
          for tag in ${TAGS}; do images+="${tag}@${DIGEST} "; done
          cosign sign --yes ${images}

GitLab CI/CD follows the same shape: the job declares an id_tokens entry named SIGSTORE_ID_TOKEN with aud: sigstore, sets COSIGN_YES: “true”, and Cosign reads the token automatically.

Where Signing Actually Pays Off

Cosign is the right pick for most teams, or AWS Signer if you run ECR managed signing, with Ratify and Gatekeeper verifying its Notation signatures in the cluster. Keyless signing works where your CI issues OIDC tokens and a public log entry is acceptable. Otherwise, use a key held in a KMS. A signature only pays off when you enforce it at three points.

  • Admission: Use a Kyverno ImageValidatingPolicy or Policy Controller ClusterImagePolicy before Kubernetes admits the workload.
  • Node: Use CRI-O’s policy.json to enforce signature requirements at runtime.
  • Pipeline: Sign every build by digest before verification or promotion.

Enforcement at those three points only tells you that an unsigned image was rejected. You still need to see which workload tried to pull it and act before the rollout stalls.

Coralogix collects Kubernetes logs, metrics, and events through OpenTelemetry (OTel) as part of Kubernetes monitoring. When an admission policy rejects an unsigned image, the ReplicaSet records a FailedCreate event that names the webhook, and an alert on that event can start your incident response before a stalled rollout goes unnoticed.

Start a free 14-day Coralogix trial and point your cluster’s admission events at it to catch signature-policy failures the moment they fire. You get cross-stack observability with rich correlation and unlimited retention across your logs, metrics, and traces.

Frequently Asked Questions About Container Image Signing

Does Container Image Signing Prevent Image Tampering After It Is Signed?

Signing proves the image’s state at signing time; any change to the manifest changes the digest, so the signature no longer verifies. It does not stop someone from pushing a new unsigned image to the same tag, which is why policies should reference digests and an admission controller should deny unsigned images.

What Is a Container Image Digest and Why Does It Matter for Signing?

A digest is the SHA-256 hash of the image manifest, and the registry hands it back in the Docker-Content-Digest header. A tag such as latest is mutable and can be reassigned to a different manifest without warning. Referencing images by digest in both cosign sign and your admission policies ensures the artifact you verified is the artifact that runs.

Can I Sign Images That Are Stored in a Private Registry?

Yes, Cosign’s registry-support tests cover ECR, Google Artifact Registry, ACR, Harbor, Artifactory, and other OCI-compliant registries. Harbor stores the signature as an artifact accessory tied to the image. Cosign reads the same Docker config and credential helpers that docker login writes to, which is why the tutorial’s login step also covers cosign sign. Keyless signing also needs network access to the public Fulcio and Rekor services unless you run a private Sigstore deployment.

How Does Keyless Signing Work Without Storing a Private Key?

Cosign generates an ephemeral key pair, sends the public key and your OIDC token to Fulcio, and gets back a short-lived certificate that Fulcio also submits to a certificate transparency log. It signs the payload, records the signature, certificate, and digest in Rekor, then discards the private key. A verifier checks the Fulcio certificate, confirms the identity and issuer match its policy, and confirms the Rekor entry’s timestamp falls inside the certificate’s validity window.

On this page