Skip to main content

GitOps with ArgoCD

Production Purpose: GitOps means Git is the single source of truth for your cluster state. You never kubectl apply directly in production — instead, you push to Git, and ArgoCD automatically detects and syncs the changes. This gives you full audit trail, rollback history, and drift detection for free.


GitOps Principles

GitOps vs Traditional Deployment

AspectTraditionalGitOps
Deploy methodkubectl apply manuallyArgoCD syncs from Git
Audit trailShell historyGit commit history
RollbackRemember exact stategit revert
Drift detectionNoneArgoCD alerts on drift
Access controlWho has kubectlWho has Git write access
Multi-env promotionManual, error-proneGit branch per environment

Repository Structure

Organize your Git repo like this:

sample-app/                        ← Application code
├── app/
├── Dockerfile
└── ...

k8s-manifests/ ← Kubernetes manifests (separate repo)
├── base/
│ ├── laravel/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── ingress.yaml
│ ├── mariadb/
│ │ └── statefulset.yaml
│ └── redis/
│ └── deployment.yaml
├── overlays/
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ └── kustomization.yaml
└── apps/
├── laravel-app.yaml ← ArgoCD Application CRD
└── monitoring.yaml

ArgoCD watches the k8s-manifests repo. Your app code repo is separate.


Install ArgoCD

kubectl create namespace argocd

kubectl apply --server-side -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Wait for all ArgoCD pods to start:

kubectl wait --namespace argocd \
--for=condition=ready pod \
--selector=app.kubernetes.io/name=argocd-server \
--timeout=120s

Expose ArgoCD via Ingress

Create: argocd-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-ingress
namespace: argocd
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true" # ArgoCD uses its own TLS
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
rules:
- host: argocd.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443

Apply:

kubectl apply -f argocd-ingress.yaml

Add to your laptop's /etc/hosts:

192.168.90.100  argocd.local

Get Initial Admin Password

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

Output:

SomeRandomGeneratedPassword123

Login at: https://argocd.local Username: admin Password: (from above)


Install 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 via CLI:

argocd login argocd.local --username admin --password <your-password> --insecure

Create ArgoCD Application (Laravel)

Create: argocd-laravel-app.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: laravel-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # Cleanup resources when App is deleted
spec:
project: default

source:
repoURL: https://github.com/pndhkm/sample-app
targetRevision: main
path: k8s/production # Path inside the repo with K8s manifests

destination:
server: https://kubernetes.default.svc # This cluster
namespace: production

syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual kubectl changes (drift correction)
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3m

Apply:

kubectl apply -f argocd-laravel-app.yaml

Add Kubernetes Manifests to Your Repo

In your sample-app repository, create the k8s/production/ directory:

Create: k8s/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

resources:
- deployment.yaml
- service.yaml
- ingress.yaml
- configmap.yaml

images:
- name: panduhakam/sample-app
newTag: "v1" # ArgoCD + CI will update this tag

Push to GitHub:

git add k8s/
git commit -m "feat: add kubernetes production manifests"
git push origin main

ArgoCD will detect the change within 3 minutes (default poll interval) and sync.


Watch ArgoCD Sync

Via CLI

argocd app get laravel-production
argocd app sync laravel-production # Manual sync (usually automatic)

Output:

Name:               laravel-production
Project: default
Server: https://kubernetes.default.svc
Namespace: production
URL: https://argocd.local/applications/laravel-production
Repo: https://github.com/pndhkm/sample-app
Target: main
Path: k8s/production
SyncStatus: Synced
HealthStatus: Healthy

Via UI

The ArgoCD UI shows a live graph of all Kubernetes objects and their health.


CI/CD Integration (GitHub Actions)

Create: .github/workflows/deploy.yaml

name: Build and Deploy

on:
push:
branches: [main]

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: |
docker build -t panduhakam/sample-app:${{ github.sha }} .

- name: Push to Docker Hub
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo $DOCKER_PASSWORD | docker login -u panduhakam --password-stdin
docker push panduhakam/sample-app:${{ github.sha }}

- name: Update Kubernetes manifests
run: |
# Update the image tag in kustomization.yaml
sed -i "s/newTag: .*/newTag: \"${{ github.sha }}\"/" k8s/production/kustomization.yaml
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add k8s/production/kustomization.yaml
git commit -m "ci: update image tag to ${{ github.sha }}"
git push

Full GitOps Flow


Rollback a Bad Deployment

Via ArgoCD UI

  1. Open https://argocd.local ArgoCD Dashboard

  2. Select the laravel-production application. ArgoCD Laravel Application

  3. Navigate to the History and Rollback tab. ArgoCD History and Rollback

  4. Select the last known good revision.

  5. Click the three-dot menu () next to the selected revision, then choose Rollback.

Via CLI

# List sync history
argocd app history laravel-production

# Rollback to previous revision
argocd app rollback laravel-production <REVISION-ID>

This reverts Git to the previous commit's state — proper rollback.


Troubleshooting

SymptomCauseFix
App shows OutOfSyncManual kubectl change madeLet ArgoCD self-heal, or sync manually
App shows DegradedPod health failingCheck pod logs and events
ArgoCD can't pull from private repoNo credentialsAdd repo via argocd repo add with SSH key
Sync fails on resource conflictExisting resources not owned by ArgoCDAdd argocd.argoproj.io/managed-by: argocd annotation
selfHeal reverts your changesselfHeal: true is setThis is correct behavior — change Git instead

Connect ArgoCD to Private GitHub Repo

argocd repo add git@github.com:pndhkm/sample-app.git \
--ssh-private-key-path ~/.ssh/id_rsa \
--name sample-app

Production Best Practices

PracticeReason
Separate app code and manifests reposClean separation of concerns
Enable selfHeal: truePrevents configuration drift
Enable prune: trueRemoves orphaned resources automatically
Use Kustomize overlays per environmentShare base manifests, override per env
Use ArgoCD ProjectsRestrict which repos/namespaces each team can deploy
Set up SSO (OIDC)Don't use local ArgoCD admin in production
Use Sealed SecretsSafe to commit encrypted Secrets to Git