Learn how ArgoCD implements GitOps principles to automate Kubernetes deployments. This guide covers core concepts, installation, and real-world patterns for managing applications declaratively.

Deploying applications to Kubernetes often starts simple: kubectl apply -f deployment.yaml. But as your infrastructure grows, manual deployments become error-prone. You lose track of what's running where, configuration drift creeps in, and rollbacks turn into panic-driven archaeology through Git history.
GitOps solves this by treating Git as the single source of truth for your infrastructure. ArgoCD is a Kubernetes-native tool that continuously monitors your Git repositories and automatically syncs changes to your clusters. Think of it as a reconciliation loop that ensures your cluster state always matches what's declared in Git.
This matters in production because it eliminates manual kubectl commands, provides audit trails through Git commits, and makes rollbacks as simple as reverting a commit. If you're managing multiple environments or teams, ArgoCD becomes essential infrastructure.
GitOps is a deployment methodology where Git repositories define the desired state of your infrastructure. Instead of running imperative commands, you declare what you want in YAML files, commit them to Git, and let automation handle the rest.
The core principles:
In traditional CI/CD, your pipeline pushes changes to production. With GitOps, your pipeline commits to Git, and ArgoCD pulls those changes into the cluster. This inversion of control improves security (no cluster credentials in CI) and observability (Git shows exactly what's deployed).
ArgoCD isn't the only GitOps tool, but it's become the de facto standard for Kubernetes. Here's why:
Kubernetes-native design - ArgoCD runs as a set of controllers inside your cluster. It understands Kubernetes resources natively and integrates with RBAC, namespaces, and custom resources.
Multi-tenancy support - You can manage hundreds of applications across multiple clusters from a single ArgoCD instance. Projects provide isolation between teams.
Flexible deployment patterns - ArgoCD supports plain YAML, Helm charts, Kustomize, and even custom config management tools. You're not locked into one approach.
Visual UI and CLI - The web interface shows real-time sync status, resource health, and diff views. The CLI enables automation and scripting.
Declarative application management - Applications themselves are defined as Kubernetes custom resources, enabling "app of apps" patterns for managing entire environments.
Before installing ArgoCD, understand these fundamental concepts:
An Application is a custom resource that defines what to deploy and where. It connects a Git repository (source) to a Kubernetes cluster (destination).
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/my-app
targetRevision: main
path: k8s/manifests
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: trueThis tells ArgoCD: "Watch the k8s/manifests directory in the main branch, and deploy it to the production namespace."
Projects provide logical grouping and access control. They define which Git repositories can be used, which clusters can be targeted, and what resources can be deployed.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-backend
namespace: argocd
spec:
description: Backend team applications
sourceRepos:
- 'https://github.com/company/backend-*'
destinations:
- namespace: 'backend-*'
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceWhitelist:
- group: 'apps'
kind: Deployment
- group: ''
kind: ServiceThis restricts the backend team to specific repositories, namespaces, and resource types.
ArgoCD continuously compares Git (desired state) with the cluster (live state):
Health is separate from sync:
A deployment can be synced but unhealthy (Git matches cluster, but pods are crashing).
Manual sync - Changes require explicit approval through UI or CLI. Use this for production environments where you want human oversight.
Automated sync - ArgoCD automatically applies changes when Git updates. Add prune: true to delete resources removed from Git, and selfHeal: true to revert manual cluster changes.
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueSync waves - Control deployment order using annotations. Lower numbers deploy first:
apiVersion: v1
kind: Service
metadata:
name: postgres
annotations:
argocd.argoproj.io/sync-wave: "0"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
annotations:
argocd.argoproj.io/sync-wave: "1"ArgoCD runs inside your Kubernetes cluster. You'll need cluster-admin access for initial setup.
kubectl configured with admin access# Create dedicated namespace
kubectl create namespace argocd
# Install ArgoCD components
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yamlYou should see pods for:
argocd-server - API server and UIargocd-repo-server - Git repository interactionargocd-application-controller - Monitors applications and syncs stateargocd-dex-server - SSO integration (optional)argocd-redis - Caching layerBy default, ArgoCD server isn't exposed externally. For initial access, use port forwarding:
kubectl port-forward svc/argocd-server -n argocd 8080:443Access the UI at https://localhost:8080. Your browser will warn about the self-signed certificate - this is expected for local development.
The initial admin password is auto-generated and stored in a secret:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -dLogin with username admin and the retrieved password.
Important
Change the admin password immediately after first login. In production, integrate with your SSO provider instead of using local accounts.
The ArgoCD CLI simplifies application management and automation:
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd
sudo mv argocd /usr/local/bin/Login to your ArgoCD instance:
argocd login localhost:8080 --username admin --password <your-password> --insecureLet's deploy a real application to understand the workflow. We'll use a simple nginx deployment.
Create a repository with Kubernetes manifests:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIPCommit and push to your Git repository.
You can create applications through the UI, CLI, or declaratively. Here's the declarative approach:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-username/your-repo
targetRevision: HEAD
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueApply it:
kubectl apply -f application.yamlOr use the CLI:
argocd app create nginx-app \
--repo https://github.com/your-username/your-repo \
--path k8s \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy automated \
--auto-prune \
--self-healWatch ArgoCD sync your application:
argocd app get nginx-appYou'll see output showing sync status, health, and deployed resources:
Check the UI at https://localhost:8080 to see the visual representation of your application's resources and their relationships.
Edit your deployment in Git (e.g., change replicas to 3), commit, and push. ArgoCD will detect the change within 3 minutes (default polling interval) and automatically sync.
Force immediate sync:
argocd app sync nginx-appArgoCD natively supports Helm without requiring Tiller or helm install commands.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prometheus
namespace: argocd
spec:
project: default
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: prometheus
targetRevision: 25.8.0
helm:
values: |
server:
persistentVolume:
enabled: false
alertmanager:
enabled: false
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=trueThis deploys Prometheus from the official Helm repository with custom values.
For complex configurations, store values in Git:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
source:
repoURL: https://github.com/your-org/helm-charts
path: charts/my-app
targetRevision: main
helm:
valueFiles:
- values-production.yaml
parameters:
- name: image.tag
value: "v1.2.3"This combines a values file from Git with parameter overrides.
Real-world deployments require separate dev, staging, and production environments. ArgoCD handles this elegantly.
Organize your repository by environment:
my-app/
├── base/
│ ├── deployment.yaml
│ └── service.yaml
├── overlays/
│ ├── dev/
│ │ └── kustomization.yaml
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ └── kustomization.yamlCreate separate applications pointing to different paths:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-dev
namespace: argocd
spec:
source:
repoURL: https://github.com/your-org/my-app
path: overlays/dev
targetRevision: main
destination:
namespace: devAlternatively, use Git branches for environments:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-staging
spec:
source:
repoURL: https://github.com/your-org/my-app
targetRevision: staging # Deploy from staging branch
path: k8sThis approach works well when environments have significantly different configurations.
Managing dozens of applications individually becomes tedious. The "app of apps" pattern uses an ArgoCD application to manage other applications.
Create a parent application that references child application manifests:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/argocd-apps
path: applications
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: trueStore child applications in the applications/ directory:
argocd-apps/
└── applications/
├── nginx-app.yaml
├── postgres-app.yaml
├── redis-app.yaml
└── api-app.yamlDeploy the root app, and ArgoCD automatically creates all child applications. Adding a new application is as simple as committing a new YAML file.
ArgoCD's application controller can consume significant resources when managing many applications. Without limits, it may get OOMKilled.
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-application-controller
spec:
template:
spec:
containers:
- name: argocd-application-controller
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2GiEnabling automated sync without prune: true means deleted resources in Git remain in the cluster. This causes configuration drift.
Always use:
syncPolicy:
automated:
prune: true
selfHeal: trueNever commit sensitive data to Git repositories. Use sealed secrets, external secret operators, or Vault integration instead.
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
spec:
encryptedData:
password: AgBh7+3kF9... # Encrypted, safe to commitDeploying everything to one namespace creates blast radius issues. Use separate namespaces per application or environment:
destination:
server: https://kubernetes.default.svc
namespace: my-app-production
syncPolicy:
syncOptions:
- CreateNamespace=trueDeploying interdependent resources simultaneously causes race conditions. Use sync waves to control order:
# Database first
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# Then application
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# Finally ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"Create separate projects for teams with restricted permissions:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-frontend
spec:
description: Frontend team applications
sourceRepos:
- 'https://github.com/company/frontend-*'
destinations:
- namespace: 'frontend-*'
server: https://kubernetes.default.svc
roles:
- name: developer
policies:
- p, proj:team-frontend:developer, applications, sync, team-frontend/*, allow
- p, proj:team-frontend:developer, applications, get, team-frontend/*, allowConfigure notifications for sync failures and health changes:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
template.app-sync-failed: |
message: Application {{.app.metadata.name}} sync failed
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"color": "danger",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}]
}]Use ArgoCD with Argo Rollouts for canary and blue-green deployments:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100
template:
spec:
containers:
- name: api
image: myapp:v2Prevent deployments during maintenance windows or business hours:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
spec:
syncWindows:
- kind: deny
schedule: '0 9-17 * * 1-5' # Deny 9 AM - 5 PM weekdays
duration: 8h
applications:
- '*'Set up Prometheus metrics and Grafana dashboards for ArgoCD:
# Application sync status
argocd_app_info{sync_status="OutOfSync"}
# Sync operation duration
argocd_app_sync_total
# Application health status
argocd_app_info{health_status="Degraded"}ArgoCD isn't always the right choice:
Simple single-cluster setups - If you're running a hobby project on one cluster with infrequent changes, ArgoCD adds complexity without much benefit. Plain kubectl or Helm might suffice.
Non-Kubernetes workloads - ArgoCD is Kubernetes-specific. For VM-based infrastructure, use Terraform with GitOps principles instead.
Extremely high-frequency deployments - If you're deploying hundreds of times per hour, ArgoCD's polling interval (minimum 3 seconds) might introduce latency. Consider webhook-based approaches.
Stateful applications requiring careful orchestration - While ArgoCD handles stateful apps, complex database migrations or multi-step upgrade procedures might need custom operators or manual intervention.
Organizations without Git discipline - GitOps requires treating Git as the source of truth. If your team frequently makes manual cluster changes or doesn't follow Git workflows, ArgoCD will fight against your processes.
ArgoCD transforms Kubernetes deployments from imperative commands into declarative, auditable, and automated workflows. By treating Git as the source of truth, you gain version control, rollback capabilities, and clear visibility into what's running in your clusters.
Start small: install ArgoCD, deploy one application, and experience the GitOps workflow. Once you see the benefits, expand to multiple environments, implement the app-of-apps pattern, and integrate with your CI pipelines.
The key insight is that ArgoCD inverts the traditional push-based deployment model. Instead of CI pushing to production, it commits to Git, and ArgoCD pulls changes into clusters. This separation improves security, enables self-healing, and makes your infrastructure truly declarative.
Next steps: explore multi-cluster management, integrate with SSO providers, and investigate Argo Rollouts for progressive delivery. The GitOps journey doesn't end with basic deployments - it's a foundation for building reliable, scalable infrastructure.