When organizations adopt GitOps, they often assume they need a Git-compliant source control management system (such as GitHub, GitLab, Bitbucket, or Gitea). However, the GitOps Principles actually never mention that Git is required. Looking closely at Principle #2, it states:
Desired state is stored in a way that enforces immutability, versioning and retains a complete version history.
This means as long as a backing store satisfies these requirements, it can be anything that’s compliant. This includes Git, S3, and OCI.
OCI as storage
The Open Container Initiative (OCI) established an open governance framework for defining an industry standard around container formats and their corresponding runtimes. It developed three different specifications: the Runtime Specification (runtime-spec
), Image Specification (image-spec
), and Distribution Specification (distribution-spec
).
Beyond container images, OCI is now widely used to store other types of content such as configuration files, Helm charts, SBOMs, and machine learning models. This is possible because the Distribution Specification defines a flexible, content-addressable registry API. ORAS (OCI Registry as Storage) built on this specification to push and pull arbitrary files as OCI artifacts. This demonstrates that the same infrastructure that powers container images can also manage general-purpose files.
Leveraging OCI for GitOps
Red Hat OpenShift GitOps 1.18 includes Argo CD 3.1, which supports using OCI as a source of truth. Argo CD previously supported OCI for Helm registries, but you can now use OCI as general storage for Kubernetes manifests. Because OpenShift includes an OCI-compliant registry out of the box, you no longer need to install or manage a Git system for your GitOps infrastructure. This reduces the footprint and makes it ideal for OpenShift clusters running on the edge. Furthermore, this approach offers the following advantages:
- Strengthen supply chain security with native support for signatures, SBOMs, and vulnerability scans
- Accelerate delivery through seamless integration with CI pipelines and automated workflows
- Simplify operations with centralized management in the same registries that store your images and applications
- Protect business-critical workloads with enhanced security by treating manifests as an artifact
Using OCI for GitOps
The process for using OCI for GitOps is pretty straightforward, as everything you need is already included in OpenShift. However, keep the following prerequisites in mind:
- The
oras
CLI (available on GitHub) - OpenShift GitOps 1.18+ installed (documentation)
- OpenShift Container Registry exposed (documentation)
Note: If you’re using CRC, the registry is already exposed for you.
There are a few options you should consider beforehand:
- Option 1: Push all your artifacts into the
openshift
project and use a single service account for pull operations. - Option 2: Push your artifacts into the same project as the application, but still use a single service account for pull operations.
- Option 3: Push your artifacts into the same project as the application and use a dedicated service account in that project for the pull operation.
For this blog post, we will use Option 3.
Pushing artifacts
Ideally, pushing artifacts should be done by an automated process in your CI pipeline or by an OpenShift administrator (especially for disconnected or air gapped infrastructure). For this demonstration, we are using an OpenShift administrator account with cluster-admin privileges.
oc login --username admin --password admin https://api.sno.cloud.chx:6443
Next, we create a project to store our manifests.
oc new-project mytest
Before we upload any manifests, we need to create an ImageStream
and an ImageStreamTag
.
oc create is -n mytest simple-go
oc create istag -n mytest simple-go:v1
Let’s create an oci-manifests
directory and populate it with a Deployment and Service.
mkdir oci-manifests
cd oci-manifests/
oc create deployment simple-go \
--image=quay.io/christianh814/simple-go:latest \
--dry-run=client -o yaml > deployment.yaml
oc create service clusterip simple-go \
--tcp=8080:8080 --dry-run=client -o yaml > service.yaml
We will need the route (exposed following this documentation) for the image registry. Let’s save it for convenience, as we’ll use it in later steps.
export OCP_IMAGE_REG_ROUTE=$(oc get routes -n openshift-image-registry default-route -o jsonpath='{.spec.host}')
Log in to this registry using oras
to test the connection. Use the OpenShift admin account and the token as a password.
oras login --insecure \
--username admin --password $(oc whoami -t) ${OCP_IMAGE_REG_ROUTE}
You should see "Login Succeeded." You can now use oras
to push the service.yaml
and deployment.yaml
manifests we just created. Make sure you use mytest
as the namespace.
oras push ${OCP_IMAGE_REG_ROUTE}/mytest/simple-go:v1 .
You can now change back to your previous working directory.
cd ..
That’s it! Again, this process should be automated or more formal, but this is a good way to test the functionality.
Setting up Argo CD
Part of the documentation that explained exporting the OpenShift registry had you extract the TLS certificates. You need to to take the contents of the tls.crt
file and provide them to Argo CD to trust. You can do this by modifying the argocd-tls-certs-cm
ConfigMap in the openshift-gitops
namespace.
Create a file called argocd-tls-certs-cm-patch.yaml
with the contents of the tls.crt
file. Replace ${OCP_IMAGE_REG_ROUTE}
with the value of your route.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-tls-certs-cm
namespace: openshift-gitops
data:
${OCP_IMAGE_REG_ROUTE}: |
-----BEGIN CERTIFICATE-----
<contents of the tls.crt file goes here>
-----END CERTIFICATE-----
Update the ConfigMap by patching it with the following command:
oc patch cm/argocd-tls-certs-cm -n openshift-gitops \
--patch-file argocd-tls-certs-cm-patch.yaml
Next, we have to create a ServiceAccount and a Token to pull the uploaded artifacts. In our case, we will create a specific ServiceAccount in the mytest
Namespace to handle this.
oc create sa imagepuller -n mytest
In order to create a long-lived token, we’ll have to create a secret to request it. You can do this with a heredoc.
oc create -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: imagepuller
namespace: mytest
annotations:
kubernetes.io/service-account.name: imagepuller
type: kubernetes.io/service-account-token
EOF
Let’s export this token so we can use it later.
export PULLER_TOKEN=$(oc get secret imagepuller -n mytest -o jsonpath='{.data.token}' | base64 -d)
To configure Argo CD to use this ServiceAccount to pull the OCI artifacts, you need to create a "repository secret." You can also use a heredoc with the exported variables we have set. Pay special attention: the url
field requires you to have oci://
as the URI.
oc apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: mytest-artifacts
namespace: openshift-gitops
labels:
argocd.argoproj.io/secret-type: repository
stringData:
username: imagepuller
password: ${PULLER_TOKEN}
type: oci
url: oci://${OCP_IMAGE_REG_ROUTE}/mytest/simple-go
EOF
Deploying the application
Now we’re ready to deploy the application. Create a simple-go-app.yaml
file with the following contents, replacing ${OCP_IMAGE_REG_ROUTE}
with your specific value.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: simple-go-oci
namespace: openshift-gitops
spec:
project: default
source:
path: .
repoURL: oci://${OCP_IMAGE_REG_ROUTE}/mytest/simple-go
targetRevision: v1
destination:
name: in-cluster
namespace: oci-demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Create this application by running:
oc apply -f simple-go-app.yaml
You should see the application synced in your Argo CD UI (Figure 1).

Summary
Many teams assume GitOps requires a Git-based system, but the principles focus on immutability, versioning, and a complete history—not Git itself. This opens the door to alternative storage back ends, including S3 and OCI registries.
With OpenShift GitOps 1.18 and Argo CD 3.1, OCI can act as a source of truth for GitOps workflows. This removes the need for a separate Git system, reduces infrastructure overhead, and is ideal for edge deployments. It also strengthens supply chain security with native support for signatures, SBOMs, and vulnerability scans while enabling centralized management of artifacts alongside container images.
This approach simplifies operations, accelerates delivery through CI integration, and provides a modern, secure foundation for GitOps in OpenShift environments.