GitOps Workflow with ArgoCD
GitOps transforms infrastructure and application management by using Git as the single source of truth. ArgoCD implements this pattern by continuously monitoring Git repositories and synchronizing desired state with live Kubernetes clusters.
GitOps Principles
GitOps establishes four core principles that guide operational practices.
Declarative Configuration
All system configuration exists as declarative files in Git:
# application-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
version: v2.1.0
spec:
containers:
- name: api
image: registry.example.com/api-service:v2.1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Declarative manifests describe the desired system state without specifying how to achieve it. ArgoCD reconciles differences between Git and the cluster automatically.
Versioned and Immutable
Git provides version control for all infrastructure changes:
# View deployment history
git log --oneline -- production/api-service/
# Compare versions
git diff v2.0.0 v2.1.0 -- production/api-service/
# Rollback to previous version
git revert HEAD
git push origin main
Every change creates a commit with authorship, timestamp, and description. This audit trail enables precise rollback to any previous state.
Pulled Automatically
ArgoCD pulls changes from Git rather than accepting pushed updates:
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant Argo as ArgoCD
participant K8s as Kubernetes Cluster
Dev->>Git: Push manifest changes
Git->>Git: Store new commit
loop Every 3 minutes
Argo->>Git: Poll for changes
Git->>Argo: Return latest commit
Argo->>Argo: Compare with cluster state
alt Changes detected
Argo->>K8s: Apply manifests
K8s->>K8s: Reconcile resources
Argo->>Argo: Update sync status
end
end
The pull model ensures cluster access credentials never leave the cluster, enhancing security by eliminating the need to distribute cluster credentials to CI/CD systems.
Continuously Reconciled
ArgoCD continuously compares Git with cluster state and corrects drift:
# ArgoCD Application with auto-sync
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/organization/k8s-manifests
path: production/api-service
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual changes
syncOptions:
- CreateNamespace=true
When someone manually modifies a resource in the cluster, self-healing reverts it to match Git within minutes, maintaining consistency.
Repository Structure
Organizing manifests efficiently supports scalable GitOps workflows.
Environment-Based Structure
Separate directories for each environment:
k8s-manifests/
├── dev/
│ ├── api-service/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── ingress.yaml
│ └── database/
│ ├── statefulset.yaml
│ └── service.yaml
├── staging/
│ ├── api-service/
│ └── database/
└── production/
├── api-service/
└── database/
Each environment has independent manifests, enabling different configurations without affecting other environments. Changes promotion follows dev -> staging -> production.
Application-Based Structure
Group by application with environment overlays:
k8s-manifests/
├── api-service/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ ├── overlays/
│ │ ├── dev/
│ │ │ └── kustomization.yaml
│ │ ├── staging/
│ │ │ └── kustomization.yaml
│ │ └── production/
│ │ └── kustomization.yaml
└── database/
├── base/
└── overlays/
Base manifests define common configuration, while overlays customize for each environment. Kustomize manages variations without duplicating YAML.
Helm-Based Structure
Use Helm charts with environment-specific values:
k8s-manifests/
├── charts/
│ └── api-service/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
└── environments/
├── dev-values.yaml
├── staging-values.yaml
└── production-values.yaml
ArgoCD applications reference the chart with different values files:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service-production
spec:
source:
repoURL: https://github.com/organization/k8s-manifests
path: charts/api-service
targetRevision: main
helm:
valueFiles:
- ../../environments/production-values.yaml
Deployment Workflows
GitOps enables various deployment patterns through Git operations.
Direct Deployment
Push changes directly to the main branch:
# Update image version
sed -i 's/v2.1.0/v2.2.0/g' production/api-service/deployment.yaml
# Commit and push
git add production/api-service/deployment.yaml
git commit -m "Deploy api-service v2.2.0 to production"
git push origin main
# ArgoCD detects and applies within 3 minutes
argocd app get api-service --refresh
Direct deployment works well for automated updates from CI pipelines and non-critical changes with low risk.
Pull Request Workflow
Review changes before deployment:
# Create feature branch
git checkout -b update-api-v2.2.0
# Make changes
sed -i 's/v2.1.0/v2.2.0/g' production/api-service/deployment.yaml
# Commit and push
git add production/api-service/deployment.yaml
git commit -m "Update api-service to v2.2.0"
git push origin update-api-v2.2.0
# Create pull request in GitHub/GitLab
# Team reviews changes
# After approval, merge to main
Pull requests enable team review, automated testing, and approval gates before production changes take effect.
graph TD
A[Developer Creates Branch] --> B[Update Manifests]
B --> C[Push to Feature Branch]
C --> D[Create Pull Request]
D --> E{Automated Checks}
E -->|Pass| F[Team Review]
E -->|Fail| G[Fix Issues]
G --> B
F -->|Approved| H[Merge to Main]
F -->|Changes Requested| G
H --> I[ArgoCD Detects Change]
I --> J[Sync to Cluster]
style H fill:#e1f5ff
style J fill:#e1f5ff
Progressive Delivery
Deploy changes gradually with traffic shifting:
# Argo Rollouts for canary deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-service
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 80
- pause: {duration: 5m}
template:
spec:
containers:
- name: api
image: registry.example.com/api-service:v2.2.0
Progressive delivery reduces risk by gradually shifting traffic to new versions while monitoring metrics. Automatic rollback triggers if errors increase.
Image Update Automation
Automatically update image tags when new versions are built.
Image Updater
ArgoCD Image Updater watches container registries and updates manifests:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
annotations:
argocd-image-updater.argoproj.io/image-list: api=registry.example.com/api-service
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$
argocd-image-updater.argoproj.io/write-back-method: git
spec:
source:
repoURL: https://github.com/organization/k8s-manifests
path: production/api-service
Image Updater polls the registry, detects new semantic versions, updates the manifest in Git, and commits the change. ArgoCD then syncs the updated manifest.
CI Pipeline Integration
CI pipelines can update manifests after building images:
# .github/workflows/deploy.yaml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build and push image
run: |
docker build -t registry.example.com/api-service:${{ github.sha }} .
docker push registry.example.com/api-service:${{ github.sha }}
- name: Update manifest
run: |
git clone https://github.com/organization/k8s-manifests
cd k8s-manifests
sed -i "s|image: registry.example.com/api-service:.*|image: registry.example.com/api-service:${{ github.sha }}|" production/api-service/deployment.yaml
git commit -am "Update api-service to ${{ github.sha }}"
git push
The CI pipeline commits the new image tag to the manifest repository after a successful build, triggering ArgoCD to deploy the update.
Secrets Management
Handle sensitive data securely within GitOps workflows.
Sealed Secrets
Encrypt secrets before committing to Git:
# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml
# Create regular secret
kubectl create secret generic api-credentials \
--from-literal=api-key=secret-value \
--dry-run=client -o yaml > secret.yaml
# Seal the secret
kubeseal -f secret.yaml -w sealed-secret.yaml
# Commit sealed secret to Git
git add sealed-secret.yaml
git commit -m "Add API credentials"
git push
The sealed secret can only be decrypted by the controller running in the cluster. Committing sealed secrets to Git is safe.
# sealed-secret.yaml (safe to commit)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: api-credentials
namespace: production
spec:
encryptedData:
api-key: AgBh9...encrypted...data...
External Secrets Operator
Reference secrets from external secret managers:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: api-credentials
creationPolicy: Owner
data:
- secretKey: api-key
remoteRef:
key: production/api-service
property: api-key
External Secrets Operator fetches secrets from HashiCorp Vault, AWS Secrets Manager, or other backends, creating Kubernetes secrets dynamically without storing sensitive values in Git.
Multi-Cluster Deployments
Manage applications across multiple Kubernetes clusters.
Cluster List
Register all target clusters:
# Register production cluster
argocd cluster add prod-cluster --name production
# Register staging cluster
argocd cluster add staging-cluster --name staging
# List registered clusters
argocd cluster list
Each cluster registration stores connection credentials, enabling ArgoCD to deploy applications to any registered cluster.
Application Per Cluster
Create separate applications for each environment:
# api-service-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service-production
spec:
project: default
source:
repoURL: https://github.com/organization/k8s-manifests
path: production/api-service
targetRevision: main
destination:
name: production
namespace: production
---
# api-service-staging.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service-staging
spec:
project: default
source:
repoURL: https://github.com/organization/k8s-manifests
path: staging/api-service
targetRevision: main
destination:
name: staging
namespace: staging
Each application points to a different cluster and manifest path, enabling independent deployment schedules and configurations.
ApplicationSet
Generate applications dynamically for multiple clusters:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: api-service
spec:
generators:
- list:
elements:
- cluster: production
url: https://prod-cluster.example.com
- cluster: staging
url: https://staging-cluster.example.com
template:
metadata:
name: 'api-service-{{cluster}}'
spec:
project: default
source:
repoURL: https://github.com/organization/k8s-manifests
path: '{{cluster}}/api-service'
targetRevision: main
destination:
server: '{{url}}'
namespace: '{{cluster}}'
syncPolicy:
automated:
prune: true
selfHeal: true
ApplicationSet reduces duplication by templating applications across clusters, simplifying multi-cluster management.
graph TB
subgraph GitRepo[Git Repository]
P[production/api-service/]
S[staging/api-service/]
end
subgraph ArgoCD
AS[ApplicationSet]
A1[Application: production]
A2[Application: staging]
end
subgraph Clusters
PC[Production Cluster]
SC[Staging Cluster]
end
AS --> A1
AS --> A2
P --> A1
S --> A2
A1 --> PC
A2 --> SC
style AS fill:#fff4e6
Monitoring and Notifications
Track deployment status and notify teams of changes.
Status Monitoring
Check application health and sync status:
# Get application status
argocd app get api-service
# Watch application sync progress
argocd app sync api-service --watch
# View application history
argocd app history api-service
ArgoCD tracks sync status, health status, and deployment history for each application, providing visibility into the current state.
Notifications
Configure notifications for sync events:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-sync-succeeded: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-sync-succeeded]
template.app-sync-succeeded: |
message: |
Application {{.app.metadata.name}} synced successfully.
Revision: {{.app.status.sync.revision}}
Author: {{.app.status.operationState.operation.initiatedBy.username}}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "good"
}]
Notifications keep teams informed of deployments, failures, and sync status through Slack, email, or other channels.
Common Pitfalls
Manual Cluster Changes: Editing resources directly in the cluster causes drift. ArgoCD reverts manual changes when self-healing is enabled, frustrating operators who make emergency fixes.
Large Manifests in Single Repo: Storing all applications in one repository causes long sync times. Separate repositories by team or application group.
Ignoring Diff Detection: Some fields change frequently (timestamps, generated values). Configure diff settings to ignore these fields, preventing constant out-of-sync status.
No Rollback Plan: Git revert provides rollback, but teams must practice it. Test rollback procedures regularly to ensure confidence during incidents.
Key Takeaways
- GitOps uses Git as the single source of truth, with ArgoCD pulling changes and reconciling cluster state automatically
- Structure repositories by environment, application, or use Helm charts with environment-specific values files
- Deploy through direct commits, pull requests for review, or progressive delivery with canary releases
- Automate image updates using ArgoCD Image Updater or CI pipeline integration
- Manage secrets with Sealed Secrets for encryption or External Secrets Operator for dynamic fetching from vaults
- Deploy to multiple clusters using separate applications or ApplicationSet for templated generation
- Monitor sync status and configure notifications to keep teams informed of deployment events
- Avoid manual cluster changes, structure repositories efficiently, and practice rollback procedures regularly