Container Image Signing#
The postgres-containers repository signs all PostgreSQL operand container images using cosign from the Sigstore ecosystem . Signing is keyless: instead of a stored private key, the CI workflow relies on short-lived OIDC tokens issued by GitHub Actions . The token issuer is https://token.actions.githubusercontent.com, and the signing identity is bound to workflows in the cloudnative-pg/postgres-containers repository .
Scope: Signing covers the
minimalandstandardimage types . Thesystemimage type is being phased out.
Two-Stage Signing: Test → Production#
Signing happens twice — once during the test build and again after promotion to production.
Stage 1 — Sign the testing image (in bake_targets.yml)#
After docker/bake-action pushes multi-arch images to the testing registry, the testbuild job immediately signs them . The comment in the workflow explains the intent: "Even if we're testing we sign the images, so we can push them to production later if that's required" . The signing step:
-
Installs cosign via
sigstore/cosign-installer@v4.1.2. -
Extracts the manifest-list digest from Bake's JSON metadata using
jq:jq '.[] | (."image.name" | sub(",.*";"")) + "@" + ."containerimage.digest"' -
Passes the
image@digestreference tocosign sign --yes, signing by digest so the signature covers the full multi-architecture manifest list, not a single platform image.
The testbuild job requires id-token: write permission for this to work .
Stage 2 — Re-sign after promotion (in copy-images action)#
After security scans pass, the copytoproduction job calls the copy-images composite action. That action:
- Copies each image from the testing registry to production using
skopeo copy -a(preserving all platform layers and the manifest list digest) . - Derives the production image reference by stripping the
-testingsuffix from the tag, then appends the originalcontainerimage.digest. - Signs the production image with
cosign sign -t 5m --yes.
The calling job must grant id-token: write — documented in the action's README as "needed by Cosign for signing the images with GitHub OIDC Token" .
Digest-Based Signing for Multi-Arch Images#
Signing by digest (image@sha256:…) is deliberate: the digest refers to the OCI image index (manifest list) produced by Docker Buildx for multi-architecture targets. This means a single signature covers all platform variants (linux/amd64, linux/arm64) rather than requiring per-platform signatures.
Key files:
.github/workflows/bake_targets.yml— build, sign (testing), security scan, promote & re-sign (production).github/actions/copy-images/action.yml— Skopeo copy + production cosign signing.github/actions/copy-images/README.md— required permissions and usage examples
Catalog File Signing#
After a successful production bake, the catalogs.yml workflow generates ClusterImageCatalog YAML files and signs each one as a blob using cosign sign-blob, writing the bundle to a .sigstore.json sidecar . This extends supply-chain integrity to the catalog manifests consumed by CloudNativePG operators.
Verifying Signatures#
cosign verify IMAGE \
--certificate-identity-regexp="^https://github.com/cloudnative-pg/postgres-containers/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
Replace IMAGE with the full ghcr.io/cloudnative-pg/postgresql:<tag>@sha256:… reference.