Deploy via ArgoCD — GitOps Path (Primary)
This is the standard production deployment path. Developers do not run helm upgrade in production. Instead, they push changes to Git — ArgoCD detects the change and drives the Helm deployment. The cluster always matches what is in Git.
GitOps Repository Structure
A dedicated repository stores all deployment configuration. It is separate from application code repos:
gitops-repo/
├── apps/
│ ├── myapp/
│ │ ├── application.yaml ← ArgoCD Application CRD
│ │ ├── values-staging.yaml ← staging overrides
│ │ └── values-prod.yaml ← production overrides
│ └── payments-api/
│ ├── application.yaml
│ ├── values-staging.yaml
│ └── values-prod.yaml
└── platform/
├── namespaces.yaml ← Terraform / Crossplane resources
└── quotas.yaml
ArgoCD watches this repo. Any commit triggers a sync check.
ArgoCD Application — Staging
# apps/myapp/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-staging
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # delete resources when app is removed
spec:
project: myteam
source:
repoURL: oci://harbor.local/charts
chart: myapp
targetRevision: "1.3.0" # pinned chart version
helm:
valueFiles:
- values-staging.yaml # loaded from the gitops repo
destination:
server: https://kubernetes.default.svc
namespace: myteam-staging
syncPolicy:
automated:
prune: true # remove resources no longer in chart
selfHeal: true # re-apply if someone manually changes cluster state
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
ArgoCD Application — Production
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
namespace: argocd
spec:
project: myteam
source:
repoURL: oci://harbor.local/charts
chart: myapp
targetRevision: "1.3.0"
helm:
valueFiles:
- values-prod.yaml
destination:
server: https://kubernetes.default.svc
namespace: myteam-prod
syncPolicy:
automated: null # NO auto-sync in production
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
Production requires a manual sync in the ArgoCD UI — a human approves every production deploy.
ArgoCD Project — Team Isolation
Each team gets an ArgoCD Project that restricts what they can deploy and where:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: myteam
namespace: argocd
spec:
description: "Payments team applications"
sourceRepos:
- oci://harbor.local/charts # only from our Harbor
destinations:
- namespace: myteam-staging
server: https://kubernetes.default.svc
- namespace: myteam-prod
server: https://kubernetes.default.svc
clusterResourceWhitelist: [] # no cluster-level resources
namespaceResourceWhitelist:
- group: "apps"
kind: "Deployment"
- group: ""
kind: "Service"
- group: "networking.k8s.io"
kind: "Ingress"
- group: "autoscaling"
kind: "HorizontalPodAutoscaler"
Teams cannot deploy to namespaces they don't own or use cluster-admin resources.
Multi-Source Application (chart + values in separate repos)
Separate the Helm chart (app repo) from the values (gitops repo):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-staging
namespace: argocd
spec:
sources:
# Source 1: Helm chart from Harbor OCI
- repoURL: oci://harbor.local/charts
chart: myapp
targetRevision: "1.3.0"
helm:
valueFiles:
- $values/apps/myapp/values-staging.yaml
# Source 2: Values files from GitOps repo
- repoURL: https://gitlab.local/platform/gitops-repo.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: myteam-staging
This is the cleanest pattern — chart and values evolve independently.
Promotion Flow: Staging → Production
1. Developer merges PR to main
2. CI bumps chart version to 1.3.0 in gitops-repo/apps/myapp/values-staging.yaml
3. ArgoCD auto-syncs → staging deploys 1.3.0
4. QA validates staging
5. Platform team (or developer with permission) updates values-prod.yaml:
targetRevision: "1.3.0"
6. Commits to gitops-repo
7. ArgoCD detects change → waits (no auto-sync in prod)
8. Developer/release manager clicks Sync in ArgoCD UI
9. Production deploys 1.3.0
Sync in ArgoCD UI
ArgoCD UI → Applications → myapp-prod
→ Status: OutOfSync (new version available)
→ Click SYNC
→ Review changes
→ Click SYNCHRONIZE
→ Watch rollout in real time
Monitor Rollout
After sync:
# Watch pods
kubectl rollout status deployment/myapp -n myteam-prod
# Check ArgoCD health
argocd app get myapp-prod
# Rollback if needed
argocd app rollback myapp-prod
Done When
✔ gitops-repo created with apps/ structure
✔ ArgoCD Application CRDs applied for staging and prod
✔ ArgoCD Project created for team isolation
✔ Staging auto-syncs on chart version bump
✔ Production requires manual sync in UI
✔ Rollback tested via argocd app rollback