Automate infrastructure and application delivery using GitOps principles. Covers repository structure, reconciliation loops, drift detection, ArgoCD patterns, and the automation patterns that make Git the single source of truth for your operational state.
A GitOps Automation Pipeline synchronizes a system’s desired state with its actual state through a continuous, automated, and declarative process, where Git is the single source of truth for infrastructure and application configurations. It matters when teams manage complex, distributed systems across multiple environments, and need to reduce drift, accelerate deployments, and ensure auditability and reproducibility of every change. This pipeline is the backbone of modern cloud-native operations, where every configuration update—whether for Kubernetes manifests, CI/CD jobs, or service policies—is tracked, versioned, and automatically applied.
The GitOps pipeline begins when a developer pushes a change to a Git repository, and ends when the system reflects that change in production. The process is fully automated: each commit triggers a sequence of events that verify, build, test, and deploy the change.
A developer modifies a Kubernetes Deployment manifest in manifests/app.yaml.
# manifests/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: app
image: registry.example.com/frontend:v1.2.3
ports:
- containerPort: 80
The change is committed and pushed to the main branch.
A CI system (e.g., GitHub Actions, GitLab CI, or Argo Workflows) runs on every push:
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
docker build -t registry.example.com/frontend:latest .
docker push registry.example.com/frontend:latest
- run: |
kubectl apply -f manifests/
kubectl get deployment frontend -o yaml
- run: |
# Run Helm chart linting
helm lint charts/frontend
The CI job fails if helm lint reports issues or kubectl get deployment returns an unexpected state.
The pipeline automatically creates a pull request with a status check: ci/ci with a link to logs. A reviewer approves the PR, and the CI job passes.
Upon merge, the pipeline triggers a deployment job. The main branch is the source of truth.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
kubectl apply -f manifests/
kubectl rollout status deployment/frontend --timeout=60s
- run: |
kubectl get deployment/frontend -o wide
- run: |
echo "Deployed to production" | tee -a /tmp/deploy.log
The rollout status command fails with a timeout if the deployment takes more than 60 seconds.
After the rollout, the pipeline runs integration tests via a test job:
# .github/workflows/test.yml
name: Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
kubectl wait --for=condition=Available --timeout=60s deployment/frontend
curl -f http://frontend.example.com/health
- run: |
kubectl get pods -l app=frontend -o wide
If curl -f fails with HTTP 404 or 500, the job fails, and a notification is sent via Slack.
The pipeline relies on a single, shared configuration repository. This repository contains all declarative artifacts, including Kubernetes manifests, Helm charts, and CI/CD workflows.
All infrastructure and application configurations are stored as YAML files in manifests/, charts/, and config/. These files are versioned and reviewed through pull requests.
replicaCount: 4
image:
repository: registry.example.com/frontend
tag: "v1.2.3"
service:
type: LoadBalancer
port: 80
Changes to values.yaml are applied via Helm, and the resulting manifest is committed to Git.
A main branch holds the production-ready configuration. Feature branches are used for experimentation.
main: live production configuration.staging: configuration for staging environment.release-v1.3: release candidate for next version.The pipeline automatically deploys staging branch to a staging cluster, and main to production.
Before applying configuration to a cluster, the pipeline validates it.
# .github/workflows/lint.yml
name: Lint
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
yq eval '.image.tag' charts/frontend/values.yaml
# Validate all manifests
kubectl apply -f manifests/ --dry-run=client -o yaml
- run: |
# Run kubeval
curl -sSfL https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64 -o kubeval
chmod +x kubeval
./kubeval --strict manifests/
The --strict flag in kubeval requires all required fields to be present. A missing spec.replicas field fails validation.
The pipeline responds to multiple event sources, each triggering a different workflow.
A push to main triggers the full deploy pipeline: CI, deployment, and post-deployment testing.
A pull request triggers CI and linting, and updates a status check.
{
"state": "success",
"description": "All tests passed",
"target_url": "https://github.com/org/repo/actions/runs/123456",
"context": "ci/ci"
}
GitHub uses this JSON in its API to update the PR status.
A team member can manually trigger a deployment via a CLI command:
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"branch": "main", "message": "Manual deployment"}' \
https://pipeline.example.com/api/v1/deploy
The server responds with a job ID:
{
"job_id": "deploy-2024-05-10-12345",
"status": "queued",
"started_at": "2024-05-10T10:30:00Z"
}
The pipeline runs a weekly maintenance job:
# .github/workflows/maintenance.yml
name: Maintenance
on:
schedule:
- cron: '0 2 * * 0' # Every Sunday at 2 AM UTC
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
kubectl get pods --all-namespaces -o wide | grep -v Running
kubectl delete pod -n monitoring -l app=grafana
The cron schedule uses standard cron syntax, and the job runs independently of Git events.
The pipeline ensures that the actual state of the system matches the desired state declared in Git.
A reconciliation loop runs every 5 minutes, comparing the current state of the cluster with the desired state in Git.
# reconcile.sh
#!/bin/bash
set -euo pipefail
BRANCH=${BRANCH:-main}
CLUSTER=${CLUSTER:-production}
# Get current state from cluster
kubectl get all -A -o yaml > /tmp/actual.yaml
# Get desired state from Git
git checkout $BRANCH
git pull origin $BRANCH
git checkout $BRANCH
# Compare with desired state
git diff --name-only HEAD~1 HEAD -- manifests/
# If differences exist, log and apply
if [ -n "$(git diff --name-only HEAD~1 HEAD -- manifests/)" ]; then
echo "Drift detected: applying changes"
kubectl apply -f manifests/
kubectl rollout status deployment/frontend --timeout=60s
fi
The script uses git diff --name-only to detect which manifest files have changed. If any file differs, it applies the changes and rolls out the deployment.
When drift is detected, the pipeline logs the differences and sends an alert.
{
"event": "drift_detected",
"branch": "main",
"timestamp": "2024-05-10T10:30:00Z",
"changes": [
{
"file": "manifests/app.yaml",
"diff": " replicas: 3\n+ replicas: 4\n",
"type": "deployment"
}
],
"details": {
"applied": true,
"rollout_status": "Successful",
"new_revision": "frontend-7584d6c98f"
}
}
The alert is sent via a webhook to a monitoring system.
The CI job runs kubectl apply, but the actual rollout is not verified. If kubectl rollout status fails silently, the deployment appears successful but the app is down.
A new image tag is pushed to the registry, but values.yaml is not updated. The Helm release uses an old image.
--timeout in rollout status: A 30-second timeout is too short for a large cluster, causing false positives.
branch in deployment job: The deploy job uses main, but the pipeline is configured for staging. The wrong branch is deployed.
--dry-run=client in validation: Without --dry-run=client, kubectl apply applies the manifest but does not validate it against the cluster’s API version.
kubectl get deployment returns Ready: 0/3: The deployment is not ready, but the pipeline assumes it is. The rollout status command must include --timeout=60s.
curl -f http://frontend.example.com/health fails on 404: The health endpoint is not available. The pipeline must ensure the service is exposed and the pod is running.
A debug job allows teams to inspect the state of the pipeline:
# .github/workflows/debug.yml
name: Debug
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to debug'
required: true
default: 'main'
jobs:
debug:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
git checkout ${{ inputs.branch }}
kubectl get all -A
kubectl get deployment/frontend -o yaml
kubectl logs deployment/frontend -n default
The workflow_dispatch allows manual triggering from the GitHub UI, with a form to specify the branch.
The GitOps Automation Pipeline is a fully automated, declarative, and event-driven system where Git is the source of truth. Every change to infrastructure and application configuration is versioned, validated, tested, and deployed. The pipeline runs continuously, reconciling the actual state of the system with the desired state in Git. It is driven by events: commits, pull requests, manual triggers, and schedules. It detects and corrects drift, and provides clear, actionable feedback when failures occur. The pipeline is built from standard tools—Git, kubectl, curl, docker, and shell scripts—and is not tied to any specific platform. Its success depends on careful configuration, consistent validation, and a clear understanding of how each component interacts.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy