# Implement GitOps with ArgoCD on Kubernetes
Introduction
GitOps is a modern approach to continuous delivery where Git is the single source of truth for your infrastructure and application definitions. ArgoCD is the most popular GitOps tool for Kubernetes, automating deployments by watching Git repositories and syncing cluster state to match.
In this tutorial, you'll build a complete GitOps pipeline: from installing ArgoCD to deploying multi-environment applications with automated sync, health monitoring, and secret management.
Prerequisites
- A Kubernetes cluster (minikube, kind, or cloud-managed)
kubectlconfiguredhelmv3 installed- A Git repository (GitHub/GitLab)
- Basic Kubernetes knowledge
Step 1: Install ArgoCD
First, install ArgoCD into your cluster:
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods to be ready
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s
# Get the initial admin password
argocd_password=$(kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d)
echo "ArgoCD Admin Password: $argocd_password"
Expose the ArgoCD server:
# Port forward for local access
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
# Install the ArgoCD CLI
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && sudo mv argocd /usr/local/bin/
# Login
argocd login localhost:8080 --username admin --password $argocd_password --insecure
Step 2: Set Up the GitOps Repository Structure
Create a well-organized Git repository:
gitops-demo/
├── apps/ # ArgoCD Application definitions
│ ├── dev/
│ │ └── web-app.yaml
│ ├── staging/
│ │ └── web-app.yaml
│ └── production/
│ └── web-app.yaml
├── base/ # Base Kubernetes manifests
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── kustomization.yaml
├── overlays/ # Environment-specific patches
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── patch-replicas.yaml
│ └── patch-resources.yaml
└── sealed-secrets/
└── web-app-secrets.yaml
Step 3: Create Base Kubernetes Manifests
base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
spec:
replicas: 1
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.25-alpine
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
envFrom:
- secretRef:
name: web-app-secrets
optional: true
base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
type: ClusterIP
selector:
app: web-app
ports:
- port: 80
targetPort: 80
protocol: TCP
base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
Step 4: Create Environment Overlays
overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
namePrefix: dev-
bases:
- ../../base
patchesStrategicMerge:
- patch-replicas.yaml
labels:
- pairs:
environment: dev
overlays/dev/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 1
overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
namePrefix: prod-
bases:
- ../../base
patchesStrategicMerge:
- patch-replicas.yaml
- patch-resources.yaml
labels:
- pairs:
environment: production
overlays/production/patch-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
replicas: 3
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
Step 5: Define ArgoCD Applications
apps/dev/web-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app-dev
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-demo.git
targetRevision: main
path: overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: dev
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
apps/production/web-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-demo.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
# Manual sync for production — no automated block
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
Apply the applications:
# Create environments
kubectl apply -f apps/dev/web-app.yaml
kubectl apply -f apps/production/web-app.yaml
# Check status
argocd app list
argocd app get web-app-dev
Step 6: Manage Secrets with Sealed Secrets
Plain Secrets in Git are a security risk. Use Sealed Secrets:
# Install Sealed Secrets controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# Install kubeseal CLI
KUBESEAL_VERSION=$(curl -s https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | grep tag_name | cut -d '"' -f4 | cut -d 'v' -f2)
curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar -xvzf kubeseal-*.tar.gz kubeseal
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
Create and seal a secret:
# Create a regular secret (don't commit this!)
kubectl create secret generic web-app-secrets \
--from-literal=DATABASE_URL='postgresql://user:pass@db:5432/app' \
--from-literal=API_KEY='sk-your-api-key-here' \
--dry-run=client -o yaml > /tmp/secret.yaml
# Seal it (safe to commit)
kubeseal --format yaml < /tmp/secret.yaml > sealed-secrets/web-app-secrets.yaml
# Clean up the plaintext
rm /tmp/secret.yaml
# The sealed secret can be safely committed to Git
cat sealed-secrets/web-app-secrets.yaml
Step 7: Implement App of Apps Pattern
Manage all applications with a single root application:
# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-demo.git
targetRevision: main
path: apps
directory:
recurse: true
include: '*.yaml'
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
kubectl apply -f apps/root-app.yaml
Step 8: Configure Notifications
Set up Slack notifications for deployment events:
# argocd-notifications-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
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]
template.app-sync-succeeded: |
message: |
✅ Application {{.app.metadata.name}} synced successfully.
Revision: {{.app.status.sync.revision}}
Environment: {{.app.spec.destination.namespace}}
template.app-sync-failed: |
message: |
❌ Application {{.app.metadata.name}} sync failed!
Revision: {{.app.status.sync.revision}}
Error: {{.app.status.operationState.message}}
Annotate applications for notifications:
kubectl patch app web-app-production -n argocd --type merge -p '
{"metadata": {"annotations": {
"notifications.argoproj.io/subscribe.on-sync-succeeded.slack": "deployments",
"notifications.argoproj.io/subscribe.on-sync-failed.slack": "deployments"
}}}'
Step 9: Implement Progressive Delivery with Argo Rollouts
Replace standard Deployments with Rollouts for canary deployments:
# Install Argo Rollouts
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
# base/rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.25-alpine
ports:
- containerPort: 80
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 2m }
- setWeight: 30
- pause: { duration: 2m }
- setWeight: 60
- pause: { duration: 2m }
- setWeight: 100
canaryService: web-app-canary
stableService: web-app-stable
trafficRouting:
nginx:
stableIngress: web-app-ingress
Monitor rollout progress:
# Install kubectl plugin
kubectl argo rollouts dashboard &
# Watch rollout
kubectl argo rollouts get rollout web-app -n production --watch
# Promote or abort
kubectl argo rollouts promote web-app -n production
kubectl argo rollouts abort web-app -n production
Step 10: Build the Promotion Pipeline
Automate environment promotion with a GitHub Actions workflow:
# .github/workflows/promote.yaml
name: Promote to Production
on:
workflow_dispatch:
inputs:
image_tag:
description: 'Image tag to promote'
required: true
jobs:
promote:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update production image
run: |
cd overlays/production
kustomize edit set image nginx=your-registry/web-app:${{ inputs.image_tag }}
- name: Commit and push
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "promote: web-app ${{ inputs.image_tag }} to production"
git push
- name: Wait for ArgoCD sync
run: |
argocd app wait web-app-production --timeout 300
argocd app get web-app-production
Useful ArgoCD Commands
# List all applications
argocd app list
# Sync an application manually
argocd app sync web-app-production
# View application diff (what would change)
argocd app diff web-app-production
# Rollback to a previous revision
argocd app rollback web-app-production 3
# View application history
argocd app history web-app-production
# Hard refresh (clear cache)
argocd app get web-app-production --hard-refresh
Summary
You've built a production-ready GitOps pipeline with:
GitOps ensures your cluster state always matches what's in Git — auditable, reproducible, and recoverable. Combined with ArgoCD's powerful UI and Argo Rollouts' progressive delivery, you have a world-class deployment platform.