You can use OCI registries as a source for Flux, letting you manage your GitOps configurations in a container registry instead of a Git repository.
Let’s see how this looks with an actual setup. Imagine you have a set of Kubernetes manifests for your application, packaged as an OCI image.
First, you’d build and push this OCI image to a registry like Docker Hub, GHCR, or your own OCI-compliant registry.
# Example: Build and push a simple Kubernetes manifest package
mkdir -p my-app-config
cat <<EOF > my-app-config/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: nginx:1.25.3
EOF
# Authenticate to your registry (e.g., Docker Hub)
docker login
# Tag and push the OCI image
IMAGE_NAME="your-dockerhub-username/my-app-config:v1.0.0"
docker build -t $IMAGE_NAME .
docker push $IMAGE_NAME
Now, you’d configure Flux to pull this OCI image as a source. This involves creating a GitRepository or HelmRepository custom resource, but pointing it to an OCI registry. For a generic set of manifests, you’ll use a GitRepository resource, but specify OCI details.
# oci-source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app-oci
namespace: flux-system
spec:
interval: 1m
url: oci://your-dockerhub-username/my-app-config
ref:
tag: v1.0.0 # Or commit for Git, but tag for OCI
# For private registries, you'd add credentials here
# secretRef:
# name: oci-registry-credentials
When Flux reconciles this GitRepository resource, it doesn’t check out a Git branch. Instead, it pulls the specified OCI image tag (v1.0.0 in this case) from the OCI URL (oci://your-dockerhub-username/my-app-config). The contents of the image are then made available to other Flux controllers as a local artifact, typically in /var/lib/fluxd/source/<repository-name>/<digest>.
The core problem this solves is decoupling your Git workflow from your Kubernetes deployments. Instead of Flux polling a Git repo, it polls an OCI registry. This allows teams to use existing CI/CD pipelines that build and push container images to publish their Kubernetes configurations. It also means you can version your entire application state (including manifests) as an immutable container image.
The internal mechanism involves Flux’s source controller. When it sees an oci:// URL, it uses an OCI-compatible client to authenticate (if credentials are provided) and pull the image. It then unpacks the image’s layers, which are expected to contain your Kubernetes manifests, into a directory that the other Flux controllers (like the Kustomize controller or Helm controller) can read. The ref field, when pointing to an OCI image, specifies either a tag or a digest. Flux will resolve this to a specific image manifest and then download the layers.
Let’s say you want to apply these manifests using the Kustomize controller. You’d create a Kustomization resource that points to this GitRepository source.
# kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app-kustomization
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: my-app-oci # Refers to the GitRepository resource above
path: "./" # Path within the OCI image artifact where manifests are located
prune: true
validation: client
Here, sourceRef.name: my-app-oci tells the Kustomize controller to use the artifact generated by the my-app-oci GitRepository source. The path: "./" indicates that the Kubernetes manifests are at the root of the unpacked OCI image artifact. Flux will then apply these manifests to your cluster.
If you’re using private registries, you’ll need to provide credentials. This is done via a Kubernetes Secret of type docker-registry or oci-registry.
apiVersion: v1
kind: Secret
metadata:
name: oci-registry-credentials
namespace: flux-system
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: |
{
"auths": {
"your-registry.example.com": {
"username": "your-username",
"password": "your-password",
"email": "your-email",
"auth": "YOUR_BASE64_ENCODED_AUTH_STRING"
}
}
}
You then reference this secret in your GitRepository spec:
spec:
# ... other spec fields
secretRef:
name: oci-registry-credentials
The most surprising thing about using OCI as a source for Flux is that the GitRepository kind is used for OCI sources. This can be a bit counterintuitive because you’re not pulling from Git at all. Flux’s source controller is designed to abstract away the underlying repository type, treating both Git and OCI as just different ways to fetch an artifact. The controller figures out what kind of URL you provided and uses the appropriate backend.
When Flux pulls an OCI image, it doesn’t just download the image layers. It creates an artifact that includes the image’s digest, manifest, and the unpacked contents. This artifact is then versioned and stored by the source controller, allowing other Flux controllers to reference a specific, immutable version of your configuration. This means if you push a new tag v1.0.1 and update your GitRepository source to use it, Flux will pull that new image, unpack it, and then the Kustomization controller will apply the new manifests.
The next concept you’ll likely encounter is managing multiple OCI sources or using OCI sources for Helm charts, which would involve HelmRepository resources configured with OCI URLs.