Flux OCI Artifacts: The Next Evolution of GitOps

Flux OCI Artifacts: The Next Evolution of GitOps

GitOps traditionally means “Git is the source of truth.” Flux’s OCI artifact support challenges that assumption in an interesting way: instead of pointing Flux at a Git repository, you can point it at an OCI image stored in a container registry. Your Kubernetes configuration is packaged, versioned, and distributed like a container image.

This is more than a deployment detail. OCI artifacts enable use cases that Git-based GitOps handles awkwardly: supply chain security with image signing, air-gapped deployments, centralized artifact management across many clusters, and audit trails that work with existing container security tooling.

What OCI Artifacts Are

OCI (Open Container Initiative) defines a standard for container images. But the OCI spec is more general than most people realize — it’s a standard for distributing arbitrary blobs of data in a layered format, with a standardized manifest format. The same registries (Harbor, Artifactory, ECR, GHCR) that store container images can store Helm charts, WASM modules, and Kubernetes configuration.

Flux supports two types of OCI artifacts:

  1. Helm charts stored in OCI registries: Instead of traditional Helm chart repositories, charts packaged and pushed to an OCI registry
  2. Flux sources as OCI artifacts: Kubernetes YAML, Kustomizations, and other resources packaged as OCI artifacts and referenced by Flux

Why OCI Over Git for Infrastructure

The traditional approach: Flux watches a Git repository and applies changes when it detects commits. This works, but has friction points:

Signature verification: Git commit signatures exist but are rarely enforced in CI/CD workflows. OCI images can be signed with Cosign, and Flux can verify those signatures before applying.

Distribution and caching: A Git repository requires network access to a Git server. OCI artifacts work with the existing container registry infrastructure your cluster already has.

Air-gapped environments: In air-gapped networks (government, regulated industries), you can mirror OCI registries more easily than maintaining Git mirrors. Zarf specifically uses OCI artifacts for air-gap package distribution.

Versioning and rollbacks: OCI artifacts are immutable by digest. You can reference config:v1.2.3 and know exactly what you’ll get, with full audit trail through the registry.

Separation of concerns: The team that builds the Helm chart releases it to a registry. The team that operates the cluster references the versioned artifact. Clean boundaries, no direct repo access required.

Publishing Flux Sources as OCI Artifacts

# Install the Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash

# Package your cluster configuration as an OCI artifact
flux push artifact oci://ghcr.io/myorg/cluster-config:$(git rev-parse --short HEAD) \
  --path="./clusters/production" \
  --source="$(git config --get remote.origin.url)" \
  --revision="$(git rev-parse HEAD)"

# Tag it as 'latest' or a version
flux tag artifact oci://ghcr.io/myorg/cluster-config:$(git rev-parse --short HEAD) \
  --tag latest

In a CI pipeline:

# .github/workflows/publish-config.yaml
name: Publish Cluster Config

on:
  push:
    branches: [main]
    paths:
    - 'clusters/**'

permissions:
  contents: read
  packages: write

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Install Flux CLI
      uses: fluxcd/flux2/action@main

    - name: Login to GHCR
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: $
        password: $

    - name: Sign artifacts with Cosign
      uses: sigstore/cosign-installer@v3

    - name: Publish and sign
      run: |
        VERSION=$(git rev-parse --short HEAD)

        flux push artifact \
          oci://ghcr.io/$/cluster-config:${VERSION} \
          --path="./clusters/production" \
          --source="$" \
          --revision="${GITHUB_SHA}"

        # Sign the artifact
        cosign sign --yes \
          ghcr.io/$/cluster-config:${VERSION}

        # Tag as latest
        flux tag artifact \
          oci://ghcr.io/$/cluster-config:${VERSION} \
          --tag latest

Configuring Flux to Use OCI Sources

# Point Flux at an OCI artifact instead of a Git repository
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: cluster-config
  namespace: flux-system
spec:
  interval: 5m
  url: oci://ghcr.io/myorg/cluster-config
  ref:
    tag: latest
  # Verify artifact signature before using
  verify:
    provider: cosign
    secretRef:
      name: cosign-public-key
---
# Apply the OCI source via Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: cluster-config
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: OCIRepository
    name: cluster-config
  path: ./
  prune: true
  wait: true

OCI Helm Charts

Helm supports OCI natively since Helm 3.8. Instead of helm repo add, you pull charts directly:

# Pull a chart from OCI (no helm repo add needed)
helm pull oci://ghcr.io/myorg/charts/myapp --version 1.2.3

# Install directly from OCI
helm install myapp oci://ghcr.io/myorg/charts/myapp \
  --version 1.2.3 \
  --namespace production \
  --values values.yaml

In Flux, reference OCI Helm charts:

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: myorg-charts
  namespace: flux-system
spec:
  type: oci  # OCI registry type
  interval: 5m
  url: oci://ghcr.io/myorg/charts
  secretRef:
    name: ghcr-credentials
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: myapp
  namespace: production
spec:
  interval: 10m
  chart:
    spec:
      chart: myapp
      version: "1.2.3"
      sourceRef:
        kind: HelmRepository
        name: myorg-charts
        namespace: flux-system
  values:
    replicaCount: 3
    ingress:
      enabled: true

Supply Chain Security with Cosign

The combination of OCI artifacts and Cosign signatures creates a verifiable supply chain:

# Generate signing keys (or use keyless signing with Sigstore)
cosign generate-key-pair

# The private key signs, public key is stored in the cluster
kubectl create secret generic cosign-public-key \
  --namespace flux-system \
  --from-file=cosign.pub=cosign.pub

Flux verifies the signature before applying:

spec:
  verify:
    provider: cosign
    secretRef:
      name: cosign-public-key
  # Flux will reject artifacts that don't have a valid signature

For keyless signing (Sigstore OIDC-based, no key management):

spec:
  verify:
    provider: cosign
    # Identity from the OIDC provider
    matchOIDCIdentity:
    - issuer: "https://token.actions.githubusercontent.com"
      subject: "repo:myorg/myrepo:ref:refs/heads/main"

This verifies that the artifact was published by a specific GitHub Actions workflow from a specific repository and branch. It’s a strong guarantee without managing signing keys.

Using OCI with Zarf for Air-Gap

Zarf uses OCI artifacts as its packaging format for air-gapped deployments. Flux and Zarf work together:

# zarf.yaml - package Flux sources for air-gap deployment
kind: ZarfPackageConfig
metadata:
  name: my-cluster-config
  version: "1.0.0"

components:
- name: flux-sources
  required: true
  images:
  - ghcr.io/myorg/cluster-config:1.0.0
  manifests:
  - name: flux-oci-source
    files:
    - manifests/oci-repository.yaml
    - manifests/kustomization.yaml

The Zarf package bundles the OCI artifact along with its Flux configuration, making the entire thing transferable to an air-gapped network.

Automating Image Updates

Flux’s Image Automation can update OCI source references when new artifacts are published:

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: cluster-config
  namespace: flux-system
spec:
  image: ghcr.io/myorg/cluster-config
  interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: cluster-config-policy
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: cluster-config
  policy:
    semver:
      range: ">=1.0.0"  # Use latest semantic version
# In your OCIRepository, Flux Image Automation will update this tag
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: cluster-config
spec:
  ref:
    tag: 1.2.3 # {"$imagepolicy": "flux-system:cluster-config:tag"}

Conclusion

OCI artifacts represent a maturation of GitOps rather than a replacement. Git remains the source of truth for configuration — but instead of Flux reading directly from Git, CI publishes signed, immutable artifacts to a registry, and Flux applies those artifacts.

The benefits compound: stronger supply chain security through signing, better compatibility with air-gapped deployments, cleaner separation between development and operations teams, and reuse of existing container registry infrastructure and security tooling.

For homelab use, the Git-based approach is simpler and probably the right call. But for production environments with security and compliance requirements — especially regulated industries, government, or defense — OCI artifacts with Cosign signing is the direction GitOps is heading.

Share this post: LinkedIn Reddit WhatsApp Mastodon
Jesse Borden

Jesse Borden

Software Engineer with an interest in hands on learning

I have several years of professional Information Technology (IT) experience leading staff and projects within the Department of War (DOW). I have managed Service Desk, Web Application Development, and System Administration teams. My two greatest passions are learning and conti...