on
Signing Helm charts in OCI registries with Cosign — hands-on guide
Helm charts are the go-to way to package and deploy Kubernetes apps. In recent years the community has moved from classic chart repositories to storing charts as OCI artifacts (so they live alongside container images), and teams are adopting Sigstore / Cosign to sign and verify those artifacts. This short, practical guide shows a modern workflow: package a chart, push it to an OCI registry, sign it with Cosign, and verify it locally or as part of a GitOps pipeline (Flux). Along the way I’ll call out common gotchas and best practices.
Why this matters
- Storing charts as OCI artifacts simplifies infrastructure: you can reuse your container registry and its access controls for charts. (v3.helm.sh)
- Signing charts (with Cosign / Sigstore) gives you provenance and tamper protection; GitOps controllers such as Flux can refuse unsigned or unverified charts. (v2-0.docs.fluxcd.io)
What you’ll need
- Helm 3.8+ (OCI support is stable in 3.8 and later). (v3.helm.sh)
- Cosign (from Sigstore) installed.
- Access to an OCI registry (e.g., GHCR, GCR, Quay, ACR, Docker Hub where OCI is supported).
- (Optional) Flux v0.35+ if you want cluster-side verification. (v2-0.docs.fluxcd.io)
Quick overview of the steps
- Prepare and package the chart.
- Login to the OCI registry.
- Push the .tgz chart as an OCI artifact.
- Sign the pushed chart with Cosign.
- Verify the signature locally and (optionally) configure Flux to verify before deploying.
Step-by-step: package, push, sign, verify
1) Package your Helm chart From the chart directory:
helm package ./my-chart
# Produces my-chart-0.1.0.tgz
2) Login to your OCI registry Helm uses the same credential stores as Docker/containers. For a generic registry:
helm registry login ghcr.io -u YOUR_USERNAME
# or for Azure Container Registry
helm registry login myregistry.azurecr.io -u <username>
If the registry requires repository pre-creation, create it first (some registries enforce this). (v3.helm.sh)
3) Push the packaged chart to the registry Push the .tgz and let Helm infer the repository basename and tag from the chart name/version:
helm push my-chart-0.1.0.tgz oci://ghcr.io/my-org/charts
# Output: Pushed: ghcr.io/my-org/charts/my-chart:0.1.0
Note: Helm strictly requires the chart name and semantic version to map to the OCI tag (no using :latest), and Helm will translate any + in a SemVer build identifier into _ when it stores the tag. (v3.helm.sh)
4) Sign the chart with Cosign You can sign with a static key or use Cosign keyless (OIDC) signing. A simple static-key example:
# generate a key pair (keep cosign.key private)
cosign generate-key-pair
# sign the pushed chart by referencing its registry ref
cosign sign --key cosign.key ghcr.io/my-org/charts/my-chart:0.1.0
This writes a signature to the registry as an OCI artifact attached to the chart. Many public projects already publish signed charts using keyless Sigstore signing; keyless uses OIDC to mint ephemeral certs. (docs.kubewarden.io)
5) Verify the signature locally Using the public key:
# verify signature using a public key you trust
cosign verify --key cosign.pub ghcr.io/my-org/charts/my-chart:0.1.0
For keyless-signed artifacts you can verify with cosign without a local key, or by matching OIDC identity patterns (issuer + subject) when automated verification is needed. (v2-0.docs.fluxcd.io)
Integrating verification into Flux (cluster-side) Flux supports verifying OCI artifacts and Helm charts signed with Cosign. You can configure an OCIRepository or a HelmRelease to require verification; if verification fails Flux won’t fetch or reconcile the artifact. Basic idea:
- Create a Kubernetes secret with your Cosign public key (or rely on keyless verification rules).
- For a Helm chart stored in an OCI HelmRepository, enable verify.provider: cosign and reference the secret (or use matchOIDCIdentity for keyless).
Example snippet (HelmRelease chart-spec verify stanza):
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
spec:
chart:
spec:
chart: my-chart
version: 0.1.0
sourceRef:
kind: HelmRepository
name: my-oci-charts
verify:
provider: cosign
secretRef:
name: cosign-pub
If verification fails Flux sets SourceVerified/Ready to False and prevents the install/upgrade. This closes the loop so only verified artifacts reach your cluster. (v2-0.docs.fluxcd.io)
Practical tips and gotchas
- Tag character differences: Helm converts
+to_when storing tags in OCI registries. Some verification tools or scripts may not account for that; test your versioning scheme. (v3.helm.sh) - Some registries require creating the repository namespace first; others create it on push — check registry behavior (e.g., GHCR, Quay, ACR differences). (v3-1-0.helm.sh)
- Choose keyless vs static keys carefully: keyless (Sigstore) gives low-friction signing via OIDC (good for GitHub Actions), but you’ll want matchers (issuer/subject) to limit which identities are trusted. Flux supports matchOIDCIdentity for this purpose. (v2-0.docs.fluxcd.io)
- Use automation in CI: package the chart in CI, push it, sign it, and then create a release tag or manifest that your release automation (Flux, Argo CD) uses. Stefan Prodan’s generic Helm chart pattern shows this kind of flow. (stefanprodan.com)
When local verification isn’t enough Local cosign verification is useful for an operator or release engineer. For scalable, enforceable security, configure your GitOps controller (Flux) or your CI/CD pipeline to verify signatures automatically. Flux’s verification feature will stop unsigned or tampered charts from being reconciled into your clusters. (v2-0.docs.fluxcd.io)
Wrap-up and next steps Storing Helm charts as OCI artifacts and signing them with Cosign gives you a modern, unified artifact workflow and stronger supply-chain guarantees. The core commands are straightforward (helm package → helm registry login → helm push → cosign sign → cosign verify), but the real value appears when verification is automated in CI/CD and enforced in cluster reconciliation (e.g., Flux). Start by trying the flow in a dev registry, then iterate:
- Add signing to your CI pipeline.
- Publish signed charts to your registry.
- Configure Flux (or your GitOps tool) to enforce verification.
References and further reading
- Official Helm docs: OCI-based registries and push/pull behavior. (v3.helm.sh)
- Flux docs: OCI artifacts and Helm chart verification with Cosign. (v2-0.docs.fluxcd.io)
- Stefan Prodan: pattern for packaging, signing, and consuming charts with Flux. (stefanprodan.com)
- CloudBees note about tag translation and Cosign quirks. (docs.cloudbees.com)
- Kubewarden and other projects that publish signed charts and verification examples (good for reference). (docs.kubewarden.io)
If you’d like, I can:
- Produce a small CI job (GitHub Actions) example that packages and signs a chart automatically, or
- Show a complete Flux HelmRelease + HelmRepository example wired to a real public registry (GHCR) with keyless Cosign verification. Which would you prefer?