ArgoCD on OpenShift: A GitOps Approach

ArgoCD in OpenShift Container Platform (OCP)

ArgoCD is a declarative, GitOps-based continuous delivery tool for Kubernetes. In OpenShift, it is available as the OpenShift GitOps operator — a Red Hat supported, enterprise-grade distribution of ArgoCD that integrates natively with OCP’s RBAC, SSO, and multi-cluster capabilities.


What is GitOps?

ArgoCD implements the GitOps pattern — Git is the single source of truth for all cluster state:

Traditional CD: GitOps with ArgoCD:
Pipeline pushes to cluster Git repo defines desired state
↓ ↓
Imperative: "run this command" ArgoCD pulls + compares continuously
↓ ↓
No drift detection Detects and corrects drift automatically
↓ ↓
Audit trail in pipeline logs Full audit trail in Git history
↓ ↓
Hard to reproduce state Cluster state is always reproducible

Installing ArgoCD on OCP — OpenShift GitOps Operator

# Install via OperatorHub (or apply YAML directly)
cat <<EOF | oc apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: openshift-gitops-operator
namespace: openshift-operators
spec:
channel: latest
name: openshift-gitops-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
# Operator automatically creates:
# - openshift-gitops namespace
# - ArgoCD instance: openshift-gitops
# - Route for ArgoCD UI
# - ClusterRoleBinding for ArgoCD to manage the cluster
# Get ArgoCD UI URL
oc get route openshift-gitops-server \
-n openshift-gitops \
-o jsonpath='{.spec.host}'
# Get initial admin password
oc extract secret/openshift-gitops-cluster \
-n openshift-gitops \
--to=-

Core ArgoCD Concepts in OCP

1. Application CRD

The Application is the fundamental ArgoCD resource — it links a Git source to a cluster destination:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payment-service
namespace: openshift-gitops # always in ArgoCD namespace
finalizers:
- resources-finalizer.argocd.argoproj.io # cascade delete
spec:
project: team-payments # AppProject for RBAC
source:
repoURL: https://github.com/contoso/gitops-repo
targetRevision: main # branch, tag, or commit SHA
path: apps/payment-service/overlays/production
destination:
server: https://kubernetes.default.svc # local cluster
namespace: payments-prod
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: true # revert manual changes to cluster
syncOptions:
- CreateNamespace=true # create namespace if missing
- PrunePropagationPolicy=foreground
- ApplyOutOfSyncOnly=true # only apply changed resources
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m

2. AppProject CRD — Multi-team RBAC

AppProject restricts what an ArgoCD application can do — which repos, which clusters, which namespaces. Essential for multi-team environments:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-payments
namespace: openshift-gitops
spec:
description: "Payment team GitOps project"
# Which Git repos this project can use
sourceRepos:
- https://github.com/contoso/payment-manifests
- https://github.com/contoso/shared-helm-charts
# Which clusters and namespaces this project can deploy to
destinations:
- server: https://kubernetes.default.svc
namespace: payments-* # wildcard — any payments namespace
- server: https://aro-prod.eastus.aroapp.io:6443
namespace: payments-prod
# Which resource types this project can manage
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: "apps"
kind: Deployment
- group: ""
kind: Service
- group: "route.openshift.io"
kind: Route
# Deny dangerous operations
namespaceResourceBlacklist:
- group: ""
kind: ResourceQuota # only platform team can set quotas
# Who can manage apps in this project
roles:
- name: payment-developer
description: "Deploy to dev only"
policies:
- p, proj:team-payments:payment-developer, applications, sync, team-payments/*, allow
- p, proj:team-payments:payment-developer, applications, get, team-payments/*, allow
groups:
- contoso:payment-developers # maps to Entra ID / LDAP group
- name: payment-lead
description: "Full access to payment project"
policies:
- p, proj:team-payments:payment-lead, applications, *, team-payments/*, allow
groups:
- contoso:payment-leads

3. ApplicationSet CRD — Generate Apps at Scale

ApplicationSet generates multiple Application resources from a template — used for multi-cluster, multi-environment deployments:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: payment-service-all-envs
namespace: openshift-gitops
spec:
generators:
# Git directory generator — one app per folder in repo
- git:
repoURL: https://github.com/contoso/gitops-repo
revision: main
directories:
- path: apps/*/overlays/* # matches apps/payment/overlays/dev, etc.
# List generator — explicit env/cluster matrix
- list:
elements:
- env: dev
cluster: https://aro-dev.eastus.aroapp.io:6443
namespace: payments-dev
- env: staging
cluster: https://aro-staging.eastus.aroapp.io:6443
namespace: payments-staging
- env: prod
cluster: https://aro-prod.eastus.aroapp.io:6443
namespace: payments-prod
# Cluster generator — deploy to ALL registered clusters
- clusters:
selector:
matchLabels:
environment: production
template:
metadata:
name: "payment-service-{{env}}"
namespace: openshift-gitops
spec:
project: team-payments
source:
repoURL: https://github.com/contoso/gitops-repo
targetRevision: main
path: "apps/payment-service/overlays/{{env}}"
destination:
server: "{{cluster}}"
namespace: "{{namespace}}"
syncPolicy:
automated:
prune: true
selfHeal: true

Source Types — Helm, Kustomize, Plain YAML

Helm chart source

spec:
source:
repoURL: https://charts.bitnami.com/bitnami
chart: postgresql
targetRevision: 12.5.6
helm:
releaseName: postgresql-prod
values: |
primary:
persistence:
size: 100Gi
storageClass: managed-premium-zrs
auth:
database: payments
existingSecret: postgresql-credentials
valueFiles:
- values-production.yaml

Kustomize overlay source (most common in OCP)

gitops-repo/
apps/payment-service/
base/
deployment.yaml
service.yaml
route.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml ← patches for dev
replica-patch.yaml
staging/
kustomization.yaml ← patches for staging
production/
kustomization.yaml ← patches for production
hpa.yaml
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: payments-prod
resources:
- ../../base
- hpa.yaml
images:
- name: payment-service
newTag: v2.4.1 # update image tag per env
patches:
- path: replica-patch.yaml
patchesStrategicMerge:
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
replicas: 5 # production: 5 replicas
template:
spec:
containers:
- name: payment-service
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi

Sync Policies — Manual vs Automated

# MANUAL sync (production best practice)
# Human must approve each sync in UI or CLI
syncPolicy: {} # no automated block
# AUTOMATED sync (dev/staging)
syncPolicy:
automated:
prune: true # delete resources removed from Git — CAREFUL in prod
selfHeal: true # revert kubectl/oc edits that drift from Git
# Automated with safeguards (staging)
syncPolicy:
automated:
prune: false # never auto-delete — require manual prune
selfHeal: true # revert config drift but don't delete

Sync operations in ArgoCD:

# Sync via CLI
argocd app sync payment-service
# Sync specific resources only
argocd app sync payment-service \
--resource apps:Deployment:payment-service
# Dry run — see what would change
argocd app diff payment-service
# Force sync (ignore resource version conflicts)
argocd app sync payment-service --force
# Sync with replace (use kubectl replace instead of apply)
argocd app sync payment-service --replace

OCP-Specific Integration

OpenShift OAuth SSO

ArgoCD on OCP uses Dex to federate authentication through OpenShift OAuth — users log in with their OCP credentials:

# ArgoCD instance with OCP OAuth (managed by GitOps operator)
apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
name: openshift-gitops
namespace: openshift-gitops
spec:
dex:
openShiftOAuth: true # use OCP OAuth automatically
rbac:
defaultPolicy: role:readonly
policy: |
# Map OCP groups to ArgoCD roles
g, contoso:platform-admins, role:admin
g, contoso:developers, role:readonly
g, contoso:payment-leads, proj:team-payments:payment-lead
scopes: '[groups]' # include group membership in token

OpenShift Routes

ArgoCD manages OpenShift Routes as native resources — no special config needed since ArgoCD uses the full OCP API:

# In your Git repo — ArgoCD applies this to OCP
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: payment-api
namespace: payments-prod
spec:
host: payment-api.apps.cluster.eastus.aroapp.io
to:
kind: Service
name: payment-service
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect

SCC Management

ArgoCD can manage SecurityContextConstraints — but needs cluster-level RBAC:

# Grant ArgoCD permission to manage SCCs
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-scc-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:openshift:scc:anyuid
subjects:
- kind: ServiceAccount
name: openshift-gitops-argocd-application-controller
namespace: openshift-gitops

Health Checks and Application Status

ArgoCD tracks two dimensions for every application:

Sync status:
Synced → cluster matches Git exactly
OutOfSync → cluster differs from Git
Unknown → cannot determine
Health status:
Healthy → all resources ready
Progressing → rolling update in progress
Degraded → pods crashing, deployment stalled
Missing → resource not found in cluster
Suspended → resource intentionally paused
Unknown → health not determinable

Custom health checks for OpenShift resources:

# argocd-cm ConfigMap — add custom health checks
data:
resource.customizations.health.route.openshift.io_Route: |
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "Admitted" and condition.status == "True" then
hs.status = "Healthy"
return hs
end
if condition.type == "Admitted" and condition.status == "False" then
hs.status = "Degraded"
hs.message = condition.message
return hs
end
end
end
return hs

Multi-Environment Promotion Pattern

The standard GitOps promotion pattern in OCP — no direct cluster access needed:

Developer workflow:
1. Write code → push to feature branch
2. CI pipeline (Tekton) runs tests + builds image
3. CI updates image tag in dev overlay:
sed -i "s/newTag:.*/newTag: v2.4.1/" overlays/dev/kustomization.yaml
git commit + push → main
4. ArgoCD auto-syncs dev environment
5. QA tests in dev
6. Developer opens PR: "promote v2.4.1 to staging"
→ updates staging/kustomization.yaml
7. Tech lead reviews + merges PR
8. ArgoCD auto-syncs staging environment
9. QA signs off staging
10. Release PR: "promote v2.4.1 to production"
→ updates production/kustomization.yaml
11. Platform team reviews + merges
12. ArgoCD detects OutOfSync on production
13. Platform team manually syncs in ArgoCD UI (with approval)
14. Production updated — full audit trail in Git history

Notifications — Slack/Teams on Sync Events

# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: openshift-gitops
data:
service.slack: |
token: $slack-token
template.app-sync-succeeded: |
slack:
attachments: |
[{
"color": "good",
"title": "✅ {{.app.metadata.name}} synced",
"text": "Deployed to {{.app.spec.destination.namespace}}\nRevision: {{.app.status.sync.revision}}"
}]
template.app-sync-failed: |
slack:
attachments: |
[{
"color": "danger",
"title": "❌ {{.app.metadata.name}} sync FAILED",
"text": "{{.app.status.operationState.message}}"
}]
trigger.on-sync-succeeded: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-sync-succeeded]
trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]

Key ArgoCD CLI Commands

# Login
argocd login openshift-gitops-server-openshift-gitops.apps.cluster.aroapp.io \
--sso # use OCP SSO
--insecure # skip TLS verify (dev only)
# Application management
argocd app list
argocd app get payment-service
argocd app diff payment-service
argocd app sync payment-service
argocd app history payment-service
argocd app rollback payment-service 3 # rollback to revision 3
argocd app delete payment-service
# Register remote cluster
argocd cluster add aro-prod-context \
--name aro-prod
# Project management
argocd proj list
argocd proj get team-payments
# Repo management
argocd repo add https://github.com/contoso/gitops-repo \
--ssh-private-key-path ~/.ssh/gitops-key
# Force refresh (re-pull from Git now)
argocd app get payment-service --refresh

Key Takeaway

ArgoCD on OCP implements GitOps as a first-class citizen — your Git repository becomes the control plane for all cluster state, every change is auditable, every deployment is reproducible, and drift from the desired state is automatically detected and optionally corrected. The OpenShift GitOps operator integrates it natively with OCP OAuth for SSO, OpenShift RBAC for multi-team access control, and OpenShift-specific resources like Routes and SCCs — making it the standard CD platform for enterprise OCP and ARO deployments.

Leave a Reply