Loading...
All Articles
CI/CD · 7 min read

GitOps with ArgoCD: A Complete Implementation Guide

Step-by-step guide to implementing GitOps with ArgoCD, from installation to advanced deployment strategies like canary releases and multi-cluster management.

What GitOps Actually Means

GitOps is an operational framework where Git is the single source of truth for your infrastructure and application configuration. Instead of running kubectl apply manually or triggering imperative scripts, you declare your desired state in Git and let an operator reconcile it automatically.

The core principles are straightforward:

  • Declarative configuration. Everything is described as code in Git.
  • Version controlled. Every change goes through a pull request with review.
  • Automated reconciliation. An agent continuously ensures the live state matches the desired state.
  • Self-healing. Drift from the desired state is detected and corrected automatically.

ArgoCD is the most widely adopted GitOps operator for Kubernetes. In this guide, we walk through a complete implementation from scratch.

Installing ArgoCD

Deploy ArgoCD into your cluster using the official manifests or Helm:

# Option 1: Official manifests
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Option 2: Helm (recommended for production)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --values argocd-values.yaml

Production Helm values:

# argocd-values.yaml
server:
  replicas: 2
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 5
  ingress:
    enabled: true
    ingressClassName: nginx
    hosts:
      - argocd.example.com
    tls:
      - secretName: argocd-tls
        hosts:
          - argocd.example.com

controller:
  replicas: 2
  resources:
    requests:
      memory: 512Mi
      cpu: 250m
    limits:
      memory: 1Gi

repoServer:
  replicas: 2
  resources:
    requests:
      memory: 256Mi
      cpu: 100m

redis-ha:
  enabled: true

configs:
  params:
    server.insecure: false
    application.instanceLabelKey: argocd.argoproj.io/instance

Retrieve the initial admin password:

kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

Repository Structure for GitOps

We recommend separating application source code from deployment manifests. This is the pattern that scales:

# Application repo (app developers own this)
my-api/
├── src/
├── Dockerfile
├── .github/workflows/build.yml
└── ...

# Deployment repo (platform team + app developers)
k8s-deployments/
├── apps/
│   ├── my-api/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   ├── hpa.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       │   ├── kustomization.yaml
│   │       │   └── patch-replicas.yaml
│   │       ├── staging/
│   │       └── production/
│   └── another-service/
├── infrastructure/
│   ├── cert-manager/
│   ├── ingress-nginx/
│   └── monitoring/
└── projects/
    └── platform.yaml

Kustomize Base and Overlays

The base contains your default manifests:

# apps/my-api/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      containers:
        - name: my-api
          image: myregistry.com/my-api:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8080

Environment overlays customize specific values:

# apps/my-api/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
  - ../../base
patches:
  - path: patch-replicas.yaml
images:
  - name: myregistry.com/my-api
    newTag: v1.4.2
# apps/my-api/overlays/production/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 5

Creating ArgoCD Applications

Define your applications as Kubernetes resources (the "App of Apps" pattern):

# projects/platform.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: platform
  namespace: argocd
spec:
  description: Platform services
  sourceRepos:
    - "https://github.com/mycompany/k8s-deployments.git"
  destinations:
    - namespace: "*"
      server: https://kubernetes.default.svc
  clusterResourceWhitelist:
    - group: "*"
      kind: "*"
# apps/my-api/argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-api-production
  namespace: argocd
  labels:
    app.kubernetes.io/part-of: platform
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: platform
  source:
    repoURL: https://github.com/mycompany/k8s-deployments.git
    targetRevision: main
    path: apps/my-api/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Key settings explained:

  • selfHeal: true -- ArgoCD reverts manual changes made with kubectl. This enforces Git as the single source of truth.
  • prune: true -- resources deleted from Git are deleted from the cluster.
  • PruneLast: true -- deletion happens after all other sync operations complete.

The CI/CD Pipeline Integration

Your CI pipeline builds and pushes images. It then updates the deployment repo to trigger ArgoCD:

# .github/workflows/build.yml (in the application repo)
name: Build and Deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: myregistry.com/my-api:${{ github.sha }}

      - name: Update deployment manifest
        run: |
          git clone https://x-access-token:${{ secrets.DEPLOY_TOKEN }}@github.com/mycompany/k8s-deployments.git
          cd k8s-deployments
          
          # Update the image tag in kustomization
          cd apps/my-api/overlays/production
          kustomize edit set image myregistry.com/my-api:${{ github.sha }}
          
          git config user.name "github-actions"
          git config user.email "[email protected]"
          git add .
          git commit -m "deploy: my-api ${{ github.sha }}"
          git push

ArgoCD detects the commit, compares the rendered manifests against the live state, and performs a sync automatically.

Progressive Delivery with Argo Rollouts

For production environments, replace standard Deployments with Argo Rollouts for canary or blue-green deployments:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-api
  namespace: production
spec:
  replicas: 5
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      containers:
        - name: my-api
          image: myregistry.com/my-api:v1.4.2
          ports:
            - containerPort: 8080
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: { duration: 5m }
        - setWeight: 30
        - pause: { duration: 5m }
        - setWeight: 60
        - pause: { duration: 5m }
      analysis:
        templates:
          - templateName: success-rate
        startingStep: 1
      canaryService: my-api-canary
      stableService: my-api-stable

---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  metrics:
    - name: success-rate
      interval: 60s
      successCondition: result[0] > 0.95
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{service="my-api",status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total{service="my-api"}[5m]))

This configuration progressively shifts traffic from 10% to 30% to 60% to 100%, with automated rollback if the success rate drops below 95%.

Multi-Cluster Management

ArgoCD can manage multiple clusters from a single control plane:

# Add a remote cluster
argocd cluster add my-staging-cluster --name staging

# Create an application targeting the remote cluster
argocd app create my-api-staging \
  --repo https://github.com/mycompany/k8s-deployments.git \
  --path apps/my-api/overlays/staging \
  --dest-server https://staging-api.example.com \
  --dest-namespace staging

For managing many applications across clusters, use ApplicationSets:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-api
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: production
            url: https://kubernetes.default.svc
          - cluster: staging
            url: https://staging-api.example.com
  template:
    metadata:
      name: "my-api-{{cluster}}"
    spec:
      project: platform
      source:
        repoURL: https://github.com/mycompany/k8s-deployments.git
        targetRevision: main
        path: "apps/my-api/overlays/{{cluster}}"
      destination:
        server: "{{url}}"
        namespace: "{{cluster}}"

Operational Best Practices

  • Use Projects to enforce RBAC. Restrict which repos and namespaces each team can deploy to.
  • Enable notifications. ArgoCD has a built-in notification controller that can send Slack messages, GitHub status updates, and webhook calls on sync events.
  • Monitor ArgoCD itself. It exposes Prometheus metrics at /metrics. Alert on sync failures and high reconciliation times.
  • Set resource limits on ArgoCD components. The repo-server in particular can consume significant memory when rendering large manifests.
  • Use sync windows to prevent deployments during maintenance windows or outside business hours.

Conclusion

GitOps with ArgoCD transforms Kubernetes deployment from an error-prone manual process into an automated, auditable, and self-healing system. The combination of declarative configuration, automated reconciliation, and progressive delivery gives teams the confidence to deploy frequently without the risk.

At DevOpsVibe, we implement GitOps pipelines that take teams from manual deployments to fully automated, multi-cluster delivery. Whether you are adopting GitOps for the first time or scaling an existing setup, we can help you get there faster. Let us know how we can help.

filed under
gitopsargocdargocdkuberneteskubernetescicddeploymentautomation
work with us

Want our team to help with your infrastructure?

talk to an engineerFree 30-min discovery callBook
close