how to sign container images with cosign
Step-by-step guide to signing container images with cosign: installing it, generating a key pair or signing keyless with OIDC, attaching the signature to the image in the registry, and verifying before deploy. Use when policy requires signed images, you are setting up supply-chain provenance, or a deploy rejects an unsigned image. Triggers: 'cosign sign', 'verify image signature'. Not for: SLSA attestations or key-management policy design.
how to sign container images with cosign
TL;DR
Cosign attaches a cryptographic signature to your container image in the registry, proving who built it. Generate a key pair (or sign keyless with OIDC so there is no key to manage), sign the image by digest, then verify the signature in CI before anything deploys. Unsigned images get rejected at the gate instead of discovered in an incident.
how to sign container images with cosignUse this when
- A policy or customer requires signed container images
- You are setting up supply-chain provenance for your builds
- A deploy pipeline rejects an image as unsigned and you need to fix the signing step
- You want to prove an image in production is the one your CI built
Not for this skill when
- You need full SLSA provenance attestations (related, but a separate skill)
- You are designing key-management or rotation policy for a whole org
- You want to sign blobs or binaries rather than OCI images (cosign does that too, different commands)
Steps
1. Install cosign and check it runs
brew install cosign
cosign versionExpected: a version string like v2.x printed. On Linux without brew, grab the release binary from the project's release page and verify its checksum before running it.
2. Generate a key pair
cosign generate-key-pairExpected: cosign.key and cosign.pub created in the working directory, with a prompt to set a password on the private key. Keep cosign.key somewhere safe (a secret manager, not the repo). The public key is what verifiers need.
3. Sign the image by digest
cosign sign --key cosign.key registry.example.com/myapp:v1.2.3Expected: output ending with "Pushing signature to:" for the registry. Cosign resolves the tag to its digest and stores the signature in the registry alongside the image. Sign the digest, not just the tag, because tags can be moved.
4. Verify the signature
cosign verify --key cosign.pub registry.example.com/myapp:v1.2.3Expected: "The signatures were verified against the specified public key" plus the signature details. Any other output means do not deploy that image.
5. Gate deploys on verification
cosign verify --key cosign.pub registry.example.com/myapp:v1.2.3 && echo SIGNED_OKExpected: SIGNED_OK printed. Put this check in CI before the deploy step so an unsigned or tampered image fails the pipeline loudly instead of reaching the cluster.
Variant: keyless signing with OIDC
Skip key management entirely. In CI with OIDC available (GitHub Actions, for example), run cosign sign registry.example.com/myapp:v1.2.3 with no --key flag. A short-lived certificate is issued via Fulcio tied to your workload identity. Verify with the certificate identity and OIDC issuer flags instead of --key. Great for CI, less great when you need long-lived offline verification.
Variant: signing with a KMS key
Point --key at a KMS reference instead of a local file when the private key must never leave the HSM. Same sign and verify flow, the key URI replaces the file path.
Variant: verification failed, now what
Do not deploy. Check you are verifying the right digest, check the key matches the signer, and check the image was not re-pushed. A genuine mismatch means the image changed after signing, treat it as tampering until proven otherwise.
Why this happens
Registries will serve any bytes you push, and tags are mutable, so "we pulled myapp:v1.2.3" proves nothing about what is actually running. Signing binds the image digest to an identity at build time. Verification at deploy time closes the loop: the cluster only runs images your pipeline actually produced.
Edge cases and pitfalls
- Signatures live in the registry, not in the image. Copying an image between registries without its signatures (plain docker pull and push) orphans them. Use a copy tool that carries signatures.
- Tag mutability: always verify the digest. Verifying a tag alone can pass against a re-pushed tag.
- Keyless certificates are short-lived. Verify promptly and keep the identity/issuer values recorded.
- cosign.key in a repo or CI log is game over for that key. Generate a new pair and re-sign.
- Some registries need explicit support for OCI artifact types to store signatures. Test with one image before wiring the whole pipeline.
Tool notes: cosign 2.x. Works with any OCI-compliant registry.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_2Za7Pn7eNZHGCiK0e1BcUA