Executive Summary & Background
In high-velocity tech teams, relying on manual deployment scripts or imperative kubectl apply commands executed from developer laptops is a primary cause of production outages.
Prior to this GitOps transformation, a growing B2B SaaS platform experienced recurring downtime during weekly production releases. Configuration drift between staging and production environments frequently caused hidden environment variable mismatches, while manual rollback procedures took upwards of 35 minutes to diagnose and resolve.
This case study breaks down how we engineered an automated, declarative GitOps continuous delivery pipeline using ArgoCD, custom Helm v3 charts, and GitHub Actions CI security gates — achieving 0ms deployment downtime and instant automated rollbacks.
The Core Challenges & Production Metrics
| Metric / Metric Area | Before GitOps Implementation | After ArgoCD & Helm Automation |
|---|---|---|
| Release Downtime | 4 – 15 minutes per release | 0ms (Zero Downtime) |
| Deployment Drift | High (100% manual drift risk) | 0% (Automated Reconciliation) |
| Rollback Resolution Time | 35+ minutes (Manual debug) | < 30 seconds (Automated) |
| Security Scanning Gate | None (Post-deploy discovery) | Automated Trivy CI Vulnerability Gate |
Target GitOps Architecture
The architecture separates concerns into two distinct pipelines:
- ▸Continuous Integration (CI): Runs in GitHub Actions. Builds Docker container images, executes unit/integration tests, runs Trivy vulnerability scans, and updates the Helm values repository.
- ▸Continuous Delivery (CD): Managed by ArgoCD inside the Kubernetes control plane. Continuously monitors the target Git manifest repository and reconciles any cluster drift within 30 seconds.
[ Developer Commit ]
│
▼
[ GitHub Actions CI ] ──▶ (Trivy Security Scan + Build Container Image)
│
▼
[ Update Helm Git Repo ]
│
▼
[ ArgoCD Control Plane ] ──▶ (Detects Drift & Syncs Declaratively)
│
▼
[ K3s / EKS Cluster ] ──▶ (Zero-Downtime Rolling Update: maxSurge 25%)
Engineering Implementation Step-by-Step
1. Custom Helm Chart Rolling Update Strategy
To guarantee zero-downtime releases, we structured custom Helm charts with strict readiness probes, liveness probes, and rolling update strategy definitions:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "saas-backend.fullname" . }}
labels:
app.kubernetes.io/name: {{ include "saas-backend.name" . }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # Spin up new pods before killing old ones
maxUnavailable: 0% # Never drop capacity below 100%
selector:
matchLabels:
app.kubernetes.io/name: {{ include "saas-backend.name" . }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "saas-backend.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz/ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz/live
port: http
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
2. ArgoCD Declarative Application Specification
We deployed ArgoCD to manage application lifecycle states declaratively. The Application manifest below enforces automated prune and self-healing mechanisms:
# argocd/application-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-backend-service
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: 'https://github.com/org/k8s-manifests.git'
targetRevision: HEAD
path: 'charts/saas-backend'
helm:
valueFiles:
- values-production.yaml
destination:
server: 'https://kubernetes.default.svc'
namespace: production
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Overwrite manual cluster modifications
syncOptions:
- CreateNamespace=true
- Validate=true
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
3. Automated Security & Vulnerability Gating (DevSecOps)
Before any Git commit triggers an ArgoCD sync, container images are scanned in GitHub Actions using Trivy. If a HIGH or CRITICAL vulnerability is detected in base dependencies, the build fails and blocks the release:
# .github/workflows/ci-pipeline.yml
name: Continuous Integration & Vulnerability Scan
on:
push:
branches: [ main ]
jobs:
security-and-build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Build Local Container Image
run: |
docker build -t saas-backend:latest .
- name: Run Trivy Vulnerability Scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'saas-backend:latest'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
- name: Push Container Image to Registry
if: success()
run: |
docker tag saas-backend:latest registry.company.com/saas-backend:latest
docker push registry.company.com/saas-backend:latest
Results & Financial Impact
- ▸100% Zero-Downtime Releases: Rolling deployment strategy with
maxUnavailable: 0%ensures that new pods pass health checks before old pods are terminated. - ▸Zero Deployment Drift: ArgoCD self-healing automatically reverts unauthorized
kubectlmutations within 30 seconds. - ▸Automated Incident Defense: If a bad image fails readiness probes during rollout, ArgoCD halts deployment progression, keeping 100% of production user traffic served by healthy previous pods.
Key Recommendations for Engineering Leaders
- ▸Never deploy without readiness probes: Without a valid readiness probe, Kubernetes will route public HTTP traffic to pods before their internal application framework has initialized.
- ▸Decouple CI from CD: Keep Docker container image building (CI) in your forge (GitHub Actions / GitLab CI), but leave cluster state reconciliation (CD) strictly to pull-based operators like ArgoCD inside the control plane.
