GitOps Basics — Declarative Deployments
In this tutorial, you'll learn about GitOps Basics. We cover key concepts, practical examples, and best practices.
GitOps is an operational framework that uses Git as the single source of truth for declarative infrastructure and application deployments, with automated reconciliation ensuring the live state matches the repository.
In this tutorial, you'll learn GitOps basics — the core principles of declarative configuration, automated reconciliation, pull-based deployments, and how to implement GitOps with Kubernetes and popular tools like Argo CD and Flux. GitOps improves deployment reliability, auditability, and developer experience by applying Git workflows to operations. By the end, you'll set up a GitOps pipeline for a sample application.
Real-world use: DodaTech uses GitOps to manage all production infrastructure across Doda Browser, DodaZIP, and Durga Antivirus Pro's backend services. Every change goes through a pull request, review, and automated sync.
flowchart TD
A[Developer pushes change] --> B[Git Repository]
B --> C{Pull Request}
C --> D[Review & Approve]
D --> E[Merge to main]
E --> F[GitOps Operator]
F --> G[Compare state]
G --> H{Desired == Actual?}
H -->|Yes| I[No action]
H -->|No| J[Apply changes]
J --> K[Update cluster state]
K --> L[Report sync status]
L --> B
Core GitOps Principles
GitOps is built on four fundamental principles.
## 1. Declarative Configuration
The entire system is described declaratively in configuration files.
No imperative scripts or manual steps. What's in the repository is
what should be running.
## 2. Git as Single Source of Truth
Git is the authoritative source for all configuration. There is no
configuration outside of Git. If it's not in the repository, it
doesn't exist in production.
## 3. Automated Reconciliation
An operator continuously compares the desired state (Git) with the
actual state (production) and corrects any drift automatically.
## 4. Pull-Based Deployments
The cluster pulls from the repository, not the other way around.
This eliminates the need for CI/CD to have cluster credentials,
improving security.
Declarative Configuration
Define your application declaratively using Kubernetes manifests.
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dodatech-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: dodatech-api
template:
metadata:
labels:
app: dodatech-api
spec:
containers:
- name: api
image: dodatech/api:v3.2.0
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
resources:
limits:
memory: "512Mi"
cpu: "500m"
---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: dodatech-api
spec:
selector:
app: dodatech-api
ports:
- port: 443
targetPort: 8080
type: LoadBalancer
Setting Up Argo CD
Argo CD is the most popular GitOps operator for Kubernetes.
# Install Argo CD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# Port forward to access the UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Login via CLI
argocd login localhost:8080 --username admin
Registering an Application
Connect Argo CD to your Git repository.
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dodatech-api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/dodatech/infrastructure.git
targetRevision: main
path: k8s/production/api
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Register the application
kubectl apply -f application.yaml
# Check sync status
argocd app get dodatech-api
# Sync manually (if auto-sync is off)
argocd app sync dodatech-api
Expected output:
$ argocd app get dodatech-api
Name: dodatech-api
Project: default
Server: https://kubernetes.default.svc
Namespace: production
URL: https://localhost:8080/applications/dodatech-api
Repo: https://github.com/dodatech/infrastructure.git
Target: main
Path: k8s/production/api
Sync Policy: Automated (Prune, Self Heal)
Sync Status: Synced
Health Status: Healthy
GitOps Workflow
A complete deployment cycle using GitOps.
# 1. Developer updates the image tag
echo 'dodatech/api:v3.3.0' > k8s/production/api/version.txt
sed -i 's|image: dodatech/api:v3.2.0|image: dodatech/api:v3.3.0|' \
k8s/production/api/deployment.yaml
# 2. Create a pull request
git checkout -b release-v3.3.0
git add k8s/production/api/
git commit -m "Release v3.3.0: update API image to v3.3.0"
git push origin release-v3.3.0
# Create PR on GitHub → team reviews → merge to main
# 3. Argo CD detects the change (auto-sync enabled)
# It automatically applies the new deployment
# The rollout happens with zero downtime (if configured)
# 4. Monitor the rollout
argocd app wait dodatech-api --health
# 5. Rollback if needed (revert the commit)
git revert HEAD
git push origin main
# Argo CD reverts automatically
Using Kustomize with GitOps
Kustomize overlays are a natural fit for GitOps workflows.
# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
images:
- name: dodatech/api
newTag: v3.2.0
# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: production
replicas:
- name: dodatech-api
count: 5
patches:
- target:
kind: Deployment
patch: |-
- op: add
path: /spec/template/spec/containers/0/env
value:
- name: LOG_LEVEL
value: info
Argo CD automatically resolves the Kustomize overlay:
# application-gitops.yaml
spec:
source:
repoURL: https://github.com/dodatech/infrastructure.git
path: k8s/overlays/production # Point to the overlay
Flux CD Alternative
Flux is another popular GitOps operator with a different approach.
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Bootstrap Flux
flux bootstrap github \
--owner=dodatech \
--repository=infrastructure \
--branch=main \
--path=./clusters/production
# Add a source (where config lives)
flux create source git dodatech-api \
--url=https://github.com/dodatech/infrastructure \
--branch=main
# Create a kustomization
flux create kustomization dodatech-api \
--source=dodatech-api \
--path="./k8s/production/api" \
--prune=true \
--interval=5m
Drift Detection and Remediation
GitOps operators continuously detect and fix configuration drift.
# Simulate drift (manual kubectl edit)
kubectl edit deployment dodatech-api -n production
# Change replicas from 3 to 2
# Argo CD detects the drift and self-heals
$ argocd app get dodatech-api
...
Sync Status: OutOfSync # ← detected drift
# Within seconds, Argo CD reverts back to 3 replicas
$ kubectl get deployment dodatech-api -n production
NAME READY UP-TO-DATE AVAILABLE AGE
dodatech-api 3/3 3 3 5m
GitOps Security Benefits
Pull-based deployments eliminate the need for cluster credentials in CI.
# Traditional CI/CD (push-based) — cluster credentials exposed
# .github/workflows/deploy.yaml
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s/ # Needs KUBECONFIG secret
# Risk: any code change can access the cluster
# GitOps (pull-based) — no credentials in CI
# .github/workflows/deploy.yaml
- name: Update config
run: |
git add k8s/
git commit -m "Update configuration"
git push
# CI only needs Git push permission, not cluster access
Common Errors
- Config drift from manual kubectl commands — Anyone with cluster access can make changes that diverge from Git. Use Argo CD's self-healing to revert, and audit permissions to prevent manual changes.
- Sync conflicts with auto-heal — If the cluster state diverges and auto-heal is on, the operator reverts changes immediately. Turn off auto-heal during incident response to allow manual overrides.
- Secrets management — Kubernetes Secrets in Git are base64-encoded, not encrypted. Use Sealed Secrets, External Secrets Operator, or SOPS to encrypt secrets before committing.
- Cluster bootstrap loop — If the GitOps operator itself isn't declared in Git, you get a bootstrap problem. Use Flux's bootstrap command or Argo CD's App of Apps pattern.
- Sync waves and ordering — Resources may need to be applied in a specific order (CRDs before CRs, namespaces before resources). Use Argo CD sync waves or Flux depends-on to control ordering.
Practice Questions
Challenge
Set up a complete GitOps pipeline. Create a GitHub repository with Kubernetes manifests for a sample application. Install Argo CD on a local Kubernetes cluster (kind or minikube). Register the application with Argo CD, enable auto-sync and self-healing. Make a change to the manifests and verify Argo CD syncs automatically. Introduce drift manually with kubectl and confirm self-healing reverts it. Then implement the same pipeline with Flux for comparison.
Real-World Task
Migrate the DodaTech backend deployment from a push-based CI/CD pipeline to GitOps. All production infrastructure for Doda Browser and Durga Antivirus Pro APIs is defined as Kubernetes manifests in the infrastructure repository. Set up Argo CD to synchronize three clusters (staging, production-eu, production-us). Configure automated sync with self-healing, set up sync waves to ensure database migrations run before application deployments, implement Sealed Secrets for database credentials, and establish a PR review process for all infrastructure changes.
Previous: GitHub Actions | Related: Web Deployment & CI/CD | Related: Kubernetes Guide
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro