Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer
Infrastructure as Code 2025-11-19 14 min read

GitOps with ArgoCD on EKS: A Practical Tutorial

Saddique Khan

DevOps / SRE Engineer & CEO, Cloud DevOps Solutions

GitOps means using Git as the single source of truth for your Kubernetes deployments. Merge a change to a YAML file in Git, and ArgoCD automatically applies it to the cluster, no kubectl apply from a pipeline, no manual hotfixes. Here is how to set it up end-to-end on EKS.

GitOps principles (quick recap)

  1. Declarative: describe desired state, not imperative commands
  2. Versioned: all changes in Git, reviewable and revertable
  3. Automated sync: system continuously reconciles actual with desired state
  4. Observed: deviations (drift) are detected and reported

ArgoCD implements all four. It runs in-cluster, watches Git, and applies changes. The pipeline’s only job is to push a new image tag to Git; ArgoCD handles the rest.

Install ArgoCD on EKS

# Create namespace
kubectl create namespace argocd

# Install ArgoCD (stable channel)
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=120s

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

# Port-forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080, login: admin / <password above>

For production, expose via ALB:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server
  namespace: argocd
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/backend-protocol: HTTPS
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:...
spec:
  rules:
    - host: argocd.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 443

Install the ArgoCD CLI

brew install argocd

# Login
argocd login argocd.example.com \
  --username admin \
  --password YOUR_PASSWORD \
  --grpc-web

Connect a Git repository

# HTTPS (public repo)
argocd repo add https://github.com/your-org/k8s-manifests

# HTTPS (private repo)
argocd repo add https://github.com/your-org/k8s-manifests \
  --username git \
  --password YOUR_GITHUB_TOKEN

# SSH (recommended for production)
argocd repo add [email protected]:your-org/k8s-manifests \
  --ssh-private-key-path ~/.ssh/argocd_deploy_key

Create your first Application

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-staging
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/k8s-manifests
    targetRevision: main
    path: apps/my-app/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: staging
  syncPolicy:
    automated:
      prune: true       # delete resources removed from Git
      selfHeal: true    # revert manual kubectl changes
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
kubectl apply -f application.yaml

# Check sync status
argocd app get my-app-staging
argocd app sync my-app-staging  # manual sync if needed

Deploy a Helm chart with ArgoCD

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-production
  namespace: argocd
spec:
  source:
    repoURL: https://github.com/your-org/k8s-manifests
    targetRevision: main
    path: helm/my-app
    helm:
      valueFiles:
        - values.yaml
        - values-production.yaml
      parameters:
        - name: image.tag
          value: "abc1234"  # updated by CI/CD pipeline
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

CI/CD → ArgoCD integration

The pipeline builds and pushes the image, then updates the image tag in Git. ArgoCD detects the change and syncs.

# .github/workflows/deploy.yml (relevant section)
- name: Update image tag in manifests
  run: |
    git clone https://github.com/your-org/k8s-manifests deploy-repo
    cd deploy-repo
    # Update the image tag in the Helm values file
    sed -i "s|tag:.*|tag: \"${GITHUB_SHA::8}\"|" helm/my-app/values-production.yaml
    git config user.email "[email protected]"
    git config user.name "CI Bot"
    git add helm/my-app/values-production.yaml
    git commit -m "deploy: update my-app to ${GITHUB_SHA::8}"
    git push

ArgoCD detects the push and syncs within 3 minutes (configurable). For faster sync, use webhook notifications.

ApplicationSets for multi-environment

Manage all environments from a single definition:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - env: staging
            namespace: staging
            values_file: values-staging.yaml
          - env: production
            namespace: production
            values_file: values-production.yaml
  template:
    metadata:
      name: 'my-app-{{env}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/your-org/k8s-manifests
        targetRevision: main
        path: helm/my-app
        helm:
          valueFiles:
            - values.yaml
            - '{{values_file}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Sealed Secrets for encrypted secrets in Git

Never store plaintext secrets in Git. Sealed Secrets encrypts them with the cluster’s public key.

# 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
brew install kubeseal

# Seal a secret
kubectl create secret generic my-db-secret \
  --from-literal=password=supersecret \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-db-secret.yaml

# Commit sealed-db-secret.yaml to Git, safe to do
git add sealed-db-secret.yaml && git commit -m "add sealed db secret"

The controller decrypts on the cluster; no one else can decrypt without the private key.

RBAC: least-privilege access

# ArgoCD RBAC policy (in argocd-rbac-cm ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # Developers can sync staging only
    p, role:developer, applications, sync, staging/*, allow
    p, role:developer, applications, get, */*, allow

    # Ops can sync all environments
    p, role:ops, applications, *, */*, allow
    p, role:ops, clusters, get, *, allow

    # Bind roles to groups (from SSO)
    g, github-org:developers, role:developer
    g, github-org:ops, role:ops

Key takeaways

  • GitOps with ArgoCD makes deployments auditable, reversible, and drift-detectable, kubectl apply from pipelines is the anti-pattern.
  • Enable selfHeal: true to automatically revert manual changes; this is the whole point of GitOps.
  • Use ApplicationSets to manage multiple environments from a single definition rather than duplicating Application manifests.
  • Sealed Secrets is the correct way to store Kubernetes secrets in Git, never commit plaintext.
  • Your pipeline’s job is to update the image tag in Git; ArgoCD does the rest. Keep these concerns separate.
  • Set up webhook notifications from GitHub to ArgoCD for near-instant sync instead of the default 3-minute polling interval.
#gitops #argocd #eks #kubernetes
Found this useful? Share it with your team or link to it from your documentation.

Need help implementing this?

Our certified engineers can handle the setup, migration, or ongoing operations for you.