Build a GitOps Deployment Platform with ArgoCD

Deploy ArgoCD on Kubernetes, connect it to a Git repository, and implement GitOps workflows with automated sync and rollback.

Domains & Technologies

Domains
GITOPSCI-CD
Technologies
KUBERNETESARGOCD

Blueprint Walkthrough

Architecture Overview

In this project you will build a complete GitOps deployment platform using ArgoCD. You will install ArgoCD on a Kubernetes cluster, connect it to a Git repository that acts as the single source of truth for application state, and implement automated continuous deployment — every merge to main triggers a deployment without any pipeline step that kubectl applies manifests directly.

This architecture eliminates the security problem of CI/CD pipelines holding cluster credentials and is the deployment model used by engineering teams at Atlassian, Zerodha, and most Kubernetes-native product companies.

Bash
+----------------------------------------------------------+
| Developer |
| | |
| +-- git push --► GitHub (main branch) |
| | |
| v |
| GitHub Actions CI |
| (build, test, push image) |
| | |
| Updates image tag |
| in manifests repo |
| | |
| v |
| Git Manifests Repo |
| (source of truth) |
| ^ |
| | polls every 3 minutes |
| | or webhook on push |
| ArgoCD (in cluster) |
| | |
| v |
| Kubernetes Cluster |
| (reconciles live state to Git state) |
+----------------------------------------------------------+
Problem Solved

With traditional push-based CI/CD, the pipeline holds a kubeconfig or service account token with broad cluster permissions. If the CI system is compromised — through a supply chain attack, a malicious PR, or stolen secrets — the attacker gains direct cluster access.

GitOps inverts this. ArgoCD runs inside the cluster and pulls from Git. No external system has cluster credentials. The only way to change the cluster state is to change Git, which requires code review and is fully auditable. Drift — when someone runs kubectl apply directly without going through Git — is detected automatically and can be auto-corrected.

Step-by-Step Implementation Guide

Step 1: Prepare Your Kubernetes Cluster and Git Repositories

You need two Git repositories. One for the application code (where developers push features), and one for the Kubernetes manifests (where ArgoCD reads from). Separating them is a GitOps best practice — it decouples the deployment concerns from the development concerns.

Bash
## Verify you have a running Kubernetes cluster
kubectl cluster-info
kubectl get nodes
## For local development, use kind or k3s
## Install kind if needed
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
## Create a local cluster
kind create cluster --name gitops-demo --config - <<EOF
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
* role: control-plane
* role: worker
* role: worker
EOF
kubectl get nodes
## Expected: 3 nodes Ready (1 control-plane, 2 workers)

Create the manifests repository structure on GitHub:

◈ DIAGRAM
gitops-manifests/
+-- apps/
| +-- web-app/
| +-- base/
| | +-- deployment.yaml
| | +-- service.yaml
| | +-- kustomization.yaml
| +-- overlays/
| +-- staging/
| | +-- kustomization.yaml
| +-- production/
| +-- kustomization.yaml
+-- argocd/
+-- applications/
+-- web-app-staging.yaml
+-- web-app-production.yaml
Remember

The manifests repository is not the application code repository. Developers commit code to the app repo. The CI pipeline (or an automated tool like ArgoCD Image Updater) commits image tag changes to the manifests repo. ArgoCD watches only the manifests repo.

Step 2: Install ArgoCD on Kubernetes

Bash
## Create the argocd namespace
kubectl create namespace argocd
## Install ArgoCD — this creates all CRDs, deployments, services, and RBAC
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
## Wait for all pods to be Running (takes 2-3 minutes)
kubectl wait --for=condition=available \
--timeout=300s \
deployment/argocd-server -n argocd
## Check all pods are running
kubectl get pods -n argocd
## Expected: argocd-server, argocd-repo-server, argocd-application-controller,
## argocd-dex-server, argocd-redis all Running
## Get the initial admin password (auto-generated)
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
## Save this password — you will need it to log in

Access the ArgoCD UI:

Bash
## Forward the ArgoCD server port to your local machine
kubectl port-forward svc/argocd-server -n argocd 8080:443
## Open https://localhost:8080 in your browser
## Username: admin
## Password: (the value from the command above)

Install the ArgoCD CLI:

Bash
curl -sSL -o argocd-linux-amd64 \
https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd-linux-amd64 && sudo mv argocd-linux-amd64 /usr/local/bin/argocd
## Log in via CLI
argocd login localhost:8080 \
--username admin \
--password YOUR_PASSWORD \
--insecure

Step 3: Create the Application Manifests with Kustomize

Kustomize allows you to have a base configuration and environment-specific overlays — without duplicating YAML files.

Create apps/web-app/base/deployment.yaml in your manifests repo:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.25-alpine # ArgoCD Image Updater will manage this tag
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10

Create apps/web-app/base/kustomization.yaml:

YAML
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml

Create the production overlay apps/web-app/overlays/production/kustomization.yaml:

YAML
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patchesStrategicMerge:
- |
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3 # Production runs 3 replicas
namePrefix: prod-
namespace: production

Commit and push these files to your manifests repo.

Step 4: Connect ArgoCD to Your Git Repository

Bash
## Add your manifests repository to ArgoCD
## For a public repo:
argocd repo add https://github.com/YOUR_USERNAME/gitops-manifests.git
## For a private repo, use an SSH key or personal access token:
argocd repo add https://github.com/YOUR_USERNAME/gitops-manifests.git \
--username YOUR_USERNAME \
--password YOUR_GITHUB_PAT
## Verify the repository was added successfully
argocd repo list
## Expected: STATUS = Successful

Step 5: Create the ArgoCD Application Resources

Create argocd/applications/web-app-production.yaml in your manifests repo:

YAML
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # Deletes cluster resources when app is deleted
spec:
project: default
source:
repoURL: https://github.com/YOUR_USERNAME/gitops-manifests.git
targetRevision: main
path: apps/web-app/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual changes to match Git state
syncOptions:
- CreateNamespace=true # Create namespace if it does not exist
- PrunePropagationPolicy=foreground
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Bash
## Apply the Application resource
kubectl apply -f argocd/applications/web-app-production.yaml
## Watch ArgoCD sync the application
argocd app get web-app-production
argocd app sync web-app-production # Manual sync for the first time
## Watch the live sync status
argocd app wait web-app-production --health --sync
Security

Enable selfHeal: true in production. Without it, a developer who runs kubectl apply directly to make an emergency change will cause permanent drift — the cluster state diverges from Git and nobody knows which state is the source of truth.

Step 6: Implement Automated Image Updates with ArgoCD Image Updater

ArgoCD Image Updater watches your container registry for new image tags and automatically commits the updated tag to your manifests repo.

Bash
## Install ArgoCD Image Updater
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml
## Annotate the Application to enable image updates
kubectl annotate application web-app-production -n argocd \
argocd-image-updater.argoproj.io/image-list="web-app=nginx" \
argocd-image-updater.argoproj.io/web-app.update-strategy=semver \
argocd-image-updater.argoproj.io/web-app.allow-tags="regexp:^1\.25\..*"

Step 7: Simulate a Rollback

The power of GitOps is that rollback is a git revert.

Bash
## Simulate a bad deployment by updating the image to a non-existent tag
## Edit apps/web-app/overlays/production/kustomization.yaml
## Change the image to: nginx:99.99-broken
git commit -am "test: use broken image tag"
git push
## Watch ArgoCD detect the change and try to sync
argocd app get web-app-production --watch
## The deployment will fail because the image does not exist
## Rollback via Git — the correct GitOps way
git revert HEAD
git push
## ArgoCD detects the revert and re-syncs to the working state
argocd app wait web-app-production --health
Tip

You can also use argocd app history web-app-production and argocd app rollback web-app-production REVISION to roll back to any previous revision from the ArgoCD CLI without touching Git. However, the Git revert method is preferred because it creates an audit trail in your repository.

Validation & Testing
Bash
## 1. Verify ArgoCD shows the app as Synced and Healthy
argocd app get web-app-production
## Expected: Health Status = Healthy, Sync Status = Synced
## 2. Check the production pods are running
kubectl get pods -n production
## Expected: 3 pods Running (prod-web-app-*)
## 3. Test drift detection — make a manual change
kubectl scale deployment prod-web-app -n production --replicas=1
## Wait 3 minutes (or trigger webhook)
argocd app get web-app-production
## Expected: Sync Status = OutOfSync (ArgoCD detected the manual change)
## With selfHeal=true, ArgoCD will automatically revert this within 3 minutes
## 4. Verify selfHeal reverted the change
kubectl get deployment prod-web-app -n production
## Expected: DESIRED = 3 (restored from Git state)
## 5. Verify no kubectl credentials exist in your CI system
## The entire deployment happened without any pipeline having cluster access
echo "GitOps deployment complete — no cluster credentials in CI"