OCI Artifacts: Distributing Container Images and Helm Charts

Package and distribute container images, Helm charts, and other artifacts using the OCI (Open Container Initiative) specification for portable artifact management.

published: reading time: 11 min read

The Open Container Initiative (OCI) Distribution Specification extends beyond container images to support any artifact type. This guide shows how to package, distribute, and sign OCI artifacts effectively.

When to Use / When Not to Use

When OCI artifacts make sense

OCI artifacts are the right choice when you want to distribute non-image artifacts through the same registry infrastructure you already use for container images. If your organization has standardized on a container registry (Harbor, ACR, GCR, ECR), OCI artifacts let you store Helm charts, SBOMs, policy bundles, and other artifacts in the same place with the same access controls and authentication.

Use OCI artifacts when you need to bundle multiple related artifacts together. ORAS lets you push a container image, its SBOM, and its signature as a single authenticated operation with one oras push command.

OCI distribution is also useful when you want vendor-neutral artifact distribution. The OCI spec is an open standard, so artifacts stored in any OCI-compatible registry are portable.

When to stick with traditional approaches

OCI artifact support requires a compatible registry. Older self-hosted registries or registries that have not updated their OCI support cannot store arbitrary artifact types. In these cases, chart repositories (for Helm) or separate artifact stores work better.

If you only distribute container images and do not need to bundle additional artifacts, standard Docker image push and pull is simpler and more widely understood.

If your artifacts are large binary files that change frequently, the registry storage costs can add up. Some artifact types are better served by object storage with a CDN.

OCI Artifact Distribution Flow

flowchart TD
    A[Developer] --> B[oras push<br/>or helm chart push]
    B --> C{Registry Type}
    C -->|OCI Compatible| D[Harbor / ACR / GCR / ECR]
    C -->|Traditional| E[ChartMuseum<br/>HTTP API]
    D --> F[Artifacts stored in<br/>OCI Registry]
    E --> G[index.yaml +<br/>chart tarballs]
    F --> H[Clusters pull via<br/>standard docker/helm pull]
    D --> I[Cosign signs<br/>manifests]
    I --> H

What is OCI?

OCI defines two specifications: the Image Specification (defines container image format) and the Distribution Specification (defines how images are distributed via APIs compatible with container registries).

The distribution specification works over HTTPS with the same registry APIs you use for Docker images. This means any OCI-compatible registry can store and serve any OCI artifact type, not just container images.

OCI artifact types include:

  • Container images (the original use case)
  • Helm charts
  • Architecture diagrams
  • Policy bundles
  • SBOMs (Software Bills of Materials)
  • Binary blobs

OCI Distribution Specification

The OCI distribution spec describes a REST API for distributing artifacts. Registries implementing this spec expose endpoints like:

GET  /v2/<name>/blobs/<digest>
POST /v2/<name>/blobs/uploads/
PUT  /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
GET  /v2/<name>/manifests/<ref>
PUT  /v2/<name>/manifests/<ref>
DELETE /v2/<name>/manifests/<ref>

Each artifact is identified by:

  • Name: Repository path (e.g., myregistry.azurecr.io/myteam/myartifact)
  • Digest: SHA256 hash of the manifest content
  • Tag: Human-readable reference (e.g., v1.0.0, latest)

Pushing Artifacts to Registries

Modern tooling supports OCI artifact push and pull directly.

Using ORAS (OCI Registry As Storage):

# Install ORAS
brew install oras

# Login to your registry
oras login myregistry.azurecr.io

# Push a generic artifact (any file)
oras push myregistry.azurecr.io/myteam/config:v1.0.0 \
  ./config.json:application/json

# Push with custom media type
oras push myregistry.azurecr.io/myteam/mydata:v1.0.0 \
  ./data.tar.gz:application/x-tar+gzip

Push Helm chart as OCI artifact:

# Login
helm registry login myregistry.azurecr.io

# Push (Helm 3.8+ supports OCI natively)
helm chart push myregistry.azurecr.io/myteam/mychart:1.0.0

# Pull
helm chart pull myregistry.azurecr.io/myteam/mychart:1.0.0
helm chart export myregistry.azurecr.io/myteam/mychart:1.0.0 ./extracted/

Push multiple files as a single artifact:

# Create artifact manifest with annotations
oras push myregistry.azurecr.io/myteam/bundle:v1.0.0 \
  ./image.tar:image/docker \
  ./sbom.spdx:application/spdx+json \
  ./signature:text/plain

Helm Charts as OCI Artifacts

Helm charts work naturally as OCI artifacts since Helm 3.8. The chart tarball becomes the artifact content, and the registry stores and serves it.

Workflow example:

# Package chart locally
helm package ./mychart

# Login to OCI registry
helm registry login -u myuser myregistry.azurecr.io

# Push with full registry path
helm chart push myregistry.azurecr.io/mycorp/charts/mychart:1.0.0

# Or pull and inspect
helm chart pull myregistry.azurecr.io/mycorp/charts/mychart:1.0.0
helm show all myregistry.azurecr.io/mycorp/charts/mychart:1.0.0

# List tags in repository
oras repo tags myregistry.azurecr.io/mycorp/charts/mychart

CI/CD integration:

# GitHub Actions example
- name: Push Helm chart to ACR
  run: |
    helm chart push ${{ env.REGISTRY }}/${{ env.CHART_NAME }}:${{ github.sha }} \
      --registry-config ${{ env.REGISTRY_CONFIG }} \
      --username ${{ env.REGISTRY_USER }} \
      --password ${{ env.REGISTRY_PASS }}

- name: Create release tag
  run: |
    helm chart push ${{ env.REGISTRY }}/${{ env.CHART_NAME }}:${{ env.VERSION }} \
      --registry-config ${{ env.REGISTRY_CONFIG }}

Cosign for Artifact Signing

Cosign, part of the Sigstore project, signs OCI artifacts and stores signatures alongside them in registries.

Installation:

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

Sign an image:

# Sign without key (for testing)
cosign sign myregistry.azurecr.io/myteam/myimage:v1.0.0

# Sign with key (production)
cosign sign --key mykey.pem myregistry.azurecr.io/myteam/myimage:v1.0.0

Sign Helm charts:

# Push chart first
helm chart push myregistry.azurecr.io/myteam/mychart:1.0.0

# Sign the chart
cosign sign --key mykey.pem myregistry.azurecr.io/myteam/mychart:1.0.0

Verify signatures:

# Verify image
cosign verify --key mykey.pem myregistry.azurecr.io/myteam/myimage:v1.0.0

# Verify with attestation (e.g., SLSA provenance)
cosign verify-attestation --type slsaprovenance \
  --key mykey.pem myregistry.azurecr.io/myteam/myimage:v1.0.0

GitHub Actions for signing:

- name: Sign image with Cosign
  uses: sigstore/cosign-installer@v3
  with:
    cosign-version: "v2"

- name: Sign container image
  run: |
    cosign sign --yes --key-env-file GOOGLE_APPLICATION_CREDENTIALS \
      ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ env.DIGEST }}

- name: Verify signature
  run: |
    cosign verify --key https://pkg.go.dev/security/augurs/cosign \
      ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ env.DIGEST }}

Practical Workflow Examples

Multi-stage artifact promotion:

#!/bin/bash
# promote-artifact.sh
ARTIFACT="$1"
FROM_TAG="$2"
TO_TAG="$3"
REGISTRY="myregistry.azurecr.io"

# Pull from source environment
oras pull $REGISTRY/$ARTIFACT:$FROM_TAG -o /tmp/artifact

# Push to target environment
oras push $REGISTRY/$ARTIFACT:$TO_TAG /tmp/artifact

# Sign the promoted artifact
cosign sign --yes $REGISTRY/$ARTIFACT:$TO_TAG

Bundling SBOM with image:

# Push image
docker push myregistry.azurecr.io/myteam/myapp:v1.0.0

# Create and push SBOM as separate artifact
cosign attest --predicate-type spdxjson \
  --certificate identity \
  --oidc-issuer https://v.stateful.so \
  myregistry.azurecr.io/myteam/myapp:sbom

# Or use ORAS for SBOM
oras push myregistry.azurecr.io/myteam/myapp:sbom \
  ./sbom.spdx:application/spdx+json

Verify before deployment:

# admission-controller policy
apiVersion: v1
kind: ConfigMap
metadata:
  name: cosign-policy
  namespace: gatekeeper-system
data:
  policy: |
    package sigstore
    deny[msg] {
      not cosign.verify(image)
      msg := "Image signature verification failed"
    }

Production Failure Scenarios

OCI artifacts can fail in ways that are specific to the distribution model.

Registry does not support OCI artifact types

If you push to a registry that has OCI artifact support disabled or using an older version, pushes may silently succeed but pulls fail or return incorrect content. Harbor requires OCI artifact support to be enabled at the project level.

Always test pull after push, especially with new registries or new artifact types.

Media type mismatches

ORAS and Helm use specific media types for artifact content. If you push with an incorrect media type, clients may not recognize the artifact when pulling. Use standard media types (application/vnd.oci.image.manifest.v1+json, application/helm.chart.v1.tar+gzip) unless you have a specific reason to deviate.

Digest mismatch after promotion

When promoting artifacts between registries (dev to staging to prod), always pull by digest, not by tag. Tags can be overwritten, causing the same tag to point to different content in different registries. Using digests ensures bit-for-bit identity across environments.

# Wrong: pull by tag (may change)
oras pull myregistry.azurecr.io/myteam/myapp:v1.0.0

# Right: pull by digest (immutable)
oras pull myregistry.azurecr.io/myteam/myapp@sha256:abc123...

Cosign signature verification failures

Cosign signatures can fail verification if the image was modified after signing (e.g., re-tagged to a different registry without re-signing). If you sign in one environment and promote to another, the signature stays valid but the target registry must be in the Cosign verification policy.

OCI vs Alternatives

AspectOCI ArtifactsChart RepositoriesObject Storage + CDN
StorageContainer registryHTTP server with index.yamlS3 / GCS / Blob
Access controlIntegrated with registry authSeparate per-repo authSeparate IAM
Artifact bundlingMultiple artifacts in one pushSingle artifact per packageManual bundling
Helm supportNative (Helm 3.8+)NativeRequires download + install
Cosign integrationNativeNot supportedNot supported
Cross-registry promotionPull by digestPull by URLCopy via CLI
CostRegistry storage ratesCheap static hostingPay per GB

OCI artifacts win when you already use a container registry and want unified artifact management. Chart repositories remain practical for pure Helm distributions without artifact bundling.

Observability Hooks

Track artifact distribution health with these monitoring practices.

Key metrics:

# Artifact push success rate
sum(rate(oras_push_success_total[5m])) by (repository, artifact_type)

# Artifact pull latency (p95)
histogram_quantile(0.95,
  sum(rate(oras_pull_duration_seconds_bucket[5m])) by (le, repository)
)

# Signature verification failures
sum(rate(cosign_verify_failure_total[5m])) by (repository, error_code)

# Registry storage used by artifact type
sum(registry_artifact_storage_bytes) by (artifact_type, repository)

Alert rules:

# Alert if artifact pushes are failing
- alert: OCIArtifactPushFailures
  expr: sum(rate(oras_push_failure_total[5m])) > 0.01
  labels:
    severity: critical
  annotations:
    summary: "OCI artifact push failures detected"
    description: "{{ $value }} push failures per second. Check registry connectivity and authentication."

# Alert if Cosign verification failures spike
- alert: CosignVerificationFailures
  expr: sum(rate(cosign_verify_failure_total[5m])) / sum(rate(cosign_verify_total[5m])) > 0.05
  labels:
    severity: critical
  annotations:
    summary: "Cosign verification failure rate above 5%"
    description: "Image signature verification is failing for {{ $labels.repository }}. Unverified images may be running in production."

# Alert if artifact storage is running low
- alert: RegistryStorageHigh
  expr: registry_storage_used_bytes / registry_storage_limit_bytes > 0.85
  labels:
    severity: warning
  annotations:
    summary: "Registry storage above 85%"
    description: "Artifact storage is running low. Plan cleanup or expansion."

Debugging commands:

# Check ORAS login status
oras login localhost:5000 -u admin -p admin

# Inspect artifact manifest
oras manifest fetch myregistry.azurecr.io/myteam/myapp:v1.0.0

# Verify artifact signature
cosign verify --key mykey.pem myregistry.azurecr.io/myteam/myapp:v1.0.0

# List all artifacts in a repository
oras repo tags myregistry.azurecr.io/myteam

# Check Helm chart in OCI registry
helm chart pull myregistry.azurecr.io/myteam/mychart:v1.0.0
helm chart export myregistry.azurecr.io/myteam/mychart:v1.0.0 /tmp/chart

# Inspect SBOM attached to image
cosign attest --type spdxjson \
  --predicate /tmp/sbom.json \
  myregistry.azurecr.io/myteam/myapp:v1.0.0

Common Pitfalls / Anti-Patterns

Pulling by mutable tag instead of immutable digest

Pushing myapp:latest and then pulling myapp:latest later may give you a different image. Tags can be overwritten. Always pull by digest for reproducible builds and deployments.

Forgetting to re-sign after cross-registry promotion

If you sign an image in your CI registry and then copy it to production with oras copy, the signature does not follow. You must sign after promoting, or use a signing service that covers both registries.

Not enabling OCI artifact support in Harbor projects

Harbor has OCI artifact support disabled by default in new projects. You must explicitly enable it in the Harbor project settings before pushing Helm charts as OCI artifacts.

Using non-standard media types without client configuration

Some registries and clients only recognize standard OCI media types. Custom media types may work locally but fail in production environments with stricter validation. Stick to standard types unless you control both the push and pull tooling.

Not cleaning up dangling manifests

When you push a manifest and then push again with the same tag, the old manifest becomes a dangling reference. Some registries do not automatically garbage collect these, leading to storage bloat. Monitor dangling manifest counts and run registry garbage collection periodically.

Quick Recap

Key Takeaways

  • OCI artifacts let you store any artifact type in standard container registries alongside your images
  • ORAS provides flexible artifact bundling; Helm 3.8+ has native OCI support for charts
  • Cosign adds cryptographic signatures that travel with the artifact through promotion pipelines
  • Always pull by digest, not by tag, for reproducible artifact distribution
  • Monitor push/pull success rates, verification failures, and storage usage

Artifact Distribution Checklist

# Push Helm chart as OCI artifact
helm registry login myregistry.azurecr.io
helm chart push myregistry.azurecr.io/myteam/mychart:1.0.0

# Sign the pushed artifact
cosign sign --key mykey.pem myregistry.azurecr.io/myteam/mychart:1.0.0

# Pull and verify
helm chart pull myregistry.azurecr.io/myteam/mychart:1.0.0
helm chart export myregistry.azurecr.io/myteam/mychart:1.0.0 /tmp/chart
cosign verify --key mykey.pem myregistry.azurecr.io/myteam/mychart:1.0.0

# Bundle SBOM with ORAS
oras push myregistry.azurecr.io/myteam/myapp:v1.0.0 \
  ./image.tar:image/docker \
  ./sbom.spdx:application/spdx+json

Promoting Artifacts Across Environments

# Pull by digest from source
oras pull myregistry.azurecr.io/myteam/myapp@sha256:abc123 -o /tmp/artifact

# Push to target registry (signature NOT included)
oras push target.azurecr.io/prod/myapp:v1.0.0 /tmp/artifact

# Re-sign in target environment
cosign sign --key prod-key.pem target.azurecr.io/prod/myapp:v1.0.0

Conclusion

OCI artifacts provide a vendor-neutral way to distribute any artifact type through standard container registries. Helm charts as OCI artifacts simplify management, and Cosign adds cryptographic verification without complex key management. For deployment patterns using these artifacts, see our GitOps and Helm Charts guides.

Category

Related Posts

Container Images: Building, Optimizing, and Distributing

Learn how Docker container images work, layer caching strategies, image optimization techniques, and how to publish your own images to container registries.

#docker #containers #devops

Container Registry: Image Storage, Scanning, and Distribution

Set up and secure container registries for storing, scanning, and distributing container images across your CI/CD pipeline and clusters.

#containers #docker #registry

Developing Helm Charts: Templates, Values, and Testing

Create production-ready Helm charts with Go templates, custom value schemas, and testing using Helm unittest and ct.

#helm #kubernetes #devops