GitOps and Continuous Delivery: ArgoCD vs Flux for Kubernetes
GitOps has emerged as the leading paradigm for Kubernetes deployments, treating Git as the single source of truth for declarative infrastructure and applications. This comprehensive guide explores GitOps principles and compares the two dominant tools: ArgoCD and Flux.
What Is GitOps?
Core Principles
- Declarative: Entire system described declaratively in Git
- Versioned and Immutable: Git commit history provides version control
- Pulled Automatically: Software agents pull desired state from Git
- Continuously Reconciled: Agents ensure actual state matches desired state
Traditional vs. GitOps Deployment
Traditional CI/CD (Push-Based):
Git Push → CI Pipeline → kubectl apply → Cluster (Direct cluster access from CI)GitOps (Pull-Based):
Git Push → Git Repository ↓ GitOps Operator (in cluster) ↓ Cluster automatically syncsBenefits of GitOps
- Audit Trail: Every change tracked in Git history
- Easy Rollback: Revert to any previous commit
- Security: No cluster credentials in CI/CD
- Disaster Recovery: Entire cluster state in Git
- Developer Experience: Familiar Git workflow
- Declarative: Infrastructure as Code principles
ArgoCD vs Flux: Feature Comparison
| Feature | ArgoCD | Flux |
|---|---|---|
| UI | Rich web UI with visualizations | No native UI (use Weave GitOps UI) |
| Multi-Tenancy | Strong (Projects, RBAC, SSO) | Good (with Flux multi-tenancy) |
| Helm Support | Native, excellent | Native via Helm Controller |
| Kustomize Support | Native | Native |
| Image Automation | Via Argo CD Image Updater | Native via Image Automation Controller |
| Progressive Delivery | Via Argo Rollouts | Via Flagger |
| Notifications | Built-in (Slack, Teams, etc.) | Via Notification Controller |
| CNCF Status | Graduated | Graduated |
| Architecture | Monolithic (easier to start) | Modular (flexible but complex) |
| Configuration | CRD-based (Application) | CRD-based (Kustomization, HelmRelease) |
| Multi-Cluster | Excellent support | Good support |
Implementing GitOps with ArgoCD
1. Installation
# Create namespacekubectl create namespace argocd
# Install ArgoCDkubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Expose ArgoCD serverkubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin passwordkubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Login with CLIargocd login localhost:8080argocd account update-password2. Repository Structure
gitops-repo/├── apps/│ ├── base/│ │ ├── kustomization.yaml│ │ ├── deployment.yaml│ │ ├── service.yaml│ │ └── ingress.yaml│ └── overlays/│ ├── dev/│ │ ├── kustomization.yaml│ │ └── patches/│ ├── staging/│ └── production/├── infrastructure/│ ├── ingress-nginx/│ ├── cert-manager/│ └── prometheus/└── clusters/ ├── dev-cluster/ │ └── applications/ ├── staging-cluster/ └── production-cluster/3. Creating an Application
Declarative Application Manifest:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-app namespace: argocd # Finalizer ensures cascading delete finalizers: - resources-finalizer.argocd.argoproj.iospec: # Project for multi-tenancy project: production
# Source repository source: repoURL: https://github.com/company/gitops-repo.git targetRevision: main path: apps/overlays/production
# For Helm charts # chart: my-chart # helm: # values: | # replicaCount: 3
# Destination cluster destination: server: https://kubernetes.default.svc namespace: web-app
# Sync policy syncPolicy: automated: prune: true # Delete resources not in Git selfHeal: true # Revert manual changes allowEmpty: false syncOptions: - CreateNamespace=true retry: limit: 5 backoff: duration: 5s factor: 2 maxDuration: 3m
# Ignore differences (for fields managed outside Git) ignoreDifferences: - group: apps kind: Deployment jsonPointers: - /spec/replicas # If HPA manages replicasApply the application:
kubectl apply -f apps/production/web-app.yaml
# Or via CLIargocd app create web-app \ --repo https://github.com/company/gitops-repo.git \ --path apps/overlays/production \ --dest-server https://kubernetes.default.svc \ --dest-namespace web-app \ --sync-policy automated \ --auto-prune \ --self-heal4. Multi-Cluster Management
# Add external cluster# First, get cluster credentialsargocd cluster add production-cluster --name production
# Create application targeting external clusterapiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-app-external namespace: argocdspec: project: default source: repoURL: https://github.com/company/gitops-repo.git path: apps/overlays/production targetRevision: main destination: name: production # Cluster name namespace: web-app syncPolicy: automated: prune: true selfHeal: true5. App of Apps Pattern
Manage multiple applications as a single unit:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: production-apps namespace: argocdspec: project: default source: repoURL: https://github.com/company/gitops-repo.git path: clusters/production/applications targetRevision: main destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true selfHeal: trueapiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: web-app namespace: argocdspec: project: production source: repoURL: https://github.com/company/gitops-repo.git path: apps/overlays/production targetRevision: main destination: server: https://kubernetes.default.svc namespace: web-app syncPolicy: automated: prune: true selfHeal: true6. Progressive Delivery with Argo Rollouts
apiVersion: argoproj.io/v1alpha1kind: Rolloutmetadata: name: web-appspec: replicas: 10 strategy: canary: steps: - setWeight: 20 # 20% traffic to new version - pause: {duration: 5m} - setWeight: 40 - pause: {duration: 5m} - setWeight: 60 - pause: {duration: 5m} - setWeight: 80 - pause: {duration: 5m} analysis: templates: - templateName: success-rate startingStep: 2 args: - name: service-name value: web-app revisionHistoryLimit: 3 selector: matchLabels: app: web-app template: metadata: labels: app: web-app spec: containers: - name: web-app image: myapp:v2.0.0 ports: - containerPort: 8080Implementing GitOps with Flux
1. Installation
# Install Flux CLIcurl -s https://fluxcd.io/install.sh | sudo bash
# Bootstrap Flux (creates components and Git repo structure)flux bootstrap github \ --owner=company \ --repository=fleet-infra \ --branch=main \ --path=clusters/production \ --personal \ --token-auth
# Flux creates:# - flux-system namespace# - GitRepository source# - Kustomization for flux-system# - Commits structure to Git repo2. Repository Structure
fleet-infra/├── clusters/│ ├── production/│ │ ├── flux-system/│ │ │ ├── gotk-components.yaml│ │ │ ├── gotk-sync.yaml│ │ │ └── kustomization.yaml│ │ ├── infrastructure.yaml│ │ └── apps.yaml│ └── staging/├── infrastructure/│ ├── sources/│ │ └── helm-repositories.yaml│ ├── ingress-nginx/│ │ ├── namespace.yaml│ │ ├── release.yaml│ │ └── values.yaml│ └── cert-manager/└── apps/ ├── base/ │ └── web-app/ └── production/ └── web-app/ ├── kustomization.yaml └── patches/3. Creating a Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: apps namespace: flux-systemspec: interval: 10m sourceRef: kind: GitRepository name: flux-system path: ./apps/production prune: true wait: true timeout: 5m validation: client healthChecks: - apiVersion: apps/v1 kind: Deployment name: web-app namespace: production4. Helm Release Management
apiVersion: source.toolkit.fluxcd.io/v1beta2kind: HelmRepositorymetadata: name: ingress-nginx namespace: flux-systemspec: interval: 24h url: https://kubernetes.github.io/ingress-nginx---# infrastructure/ingress-nginx/release.yamlapiVersion: helm.toolkit.fluxcd.io/v2beta1kind: HelmReleasemetadata: name: ingress-nginx namespace: ingress-nginxspec: interval: 30m chart: spec: chart: ingress-nginx version: "4.x" sourceRef: kind: HelmRepository name: ingress-nginx namespace: flux-system values: controller: replicaCount: 3 resources: requests: cpu: 100m memory: 128Mi upgrade: remediation: retries: 3 rollback: cleanupOnFail: true5. Image Automation
apiVersion: image.toolkit.fluxcd.io/v1beta2kind: ImageRepositorymetadata: name: web-app namespace: flux-systemspec: image: ghcr.io/company/web-app interval: 1m---# apps/production/image-policy.yamlapiVersion: image.toolkit.fluxcd.io/v1beta2kind: ImagePolicymetadata: name: web-app namespace: flux-systemspec: imageRepositoryRef: name: web-app policy: semver: range: ">=1.0.0"---# apps/production/image-update.yamlapiVersion: image.toolkit.fluxcd.io/v1beta1kind: ImageUpdateAutomationmetadata: name: web-app namespace: flux-systemspec: interval: 1m sourceRef: kind: GitRepository name: flux-system git: checkout: ref: branch: main commit: author: email: fluxcdbot@users.noreply.github.com name: fluxcdbot messageTemplate: | Automated image update
Automation name: {{ .AutomationObject }}
Files: {{ range $filename, $_ := .Updated.Files -}} - {{ $filename }} {{ end -}}
Objects: {{ range $resource, $_ := .Updated.Objects -}} - {{ $resource.Kind }} {{ $resource.Name }} {{ end -}} update: path: ./apps/production strategy: SettersAdd image policy marker in deployment:
apiVersion: apps/v1kind: Deploymentmetadata: name: web-appspec: template: spec: containers: - name: web-app image: ghcr.io/company/web-app:1.0.0 # {"$imagepolicy": "flux-system:web-app"}6. Progressive Delivery with Flagger
# Install Flaggerflux create source helm flagger \ --url=https://flagger.app \ --namespace=flux-system
flux create helmrelease flagger \ --source=HelmRepository/flagger \ --chart=flagger \ --namespace=flagger-system \ --create-target-namespace
# Create Canary deploymentapiVersion: flagger.app/v1beta1kind: Canarymetadata: name: web-app namespace: productionspec: targetRef: apiVersion: apps/v1 kind: Deployment name: web-app service: port: 80 analysis: interval: 1m threshold: 5 maxWeight: 50 stepWeight: 10 metrics: - name: request-success-rate thresholdRange: min: 99 interval: 1m - name: request-duration thresholdRange: max: 500 interval: 1m webhooks: - name: load-test url: http://flagger-loadtester/ timeout: 5s metadata: cmd: "hey -z 1m -q 10 -c 2 http://web-app-canary/"Best Practices
1. Repository Organization
Mono-repo vs. Multi-repo:
Mono-repo (Recommended for most):
gitops/├── infrastructure/ # Shared infrastructure├── apps/ # Applications└── clusters/ # Per-cluster configsMulti-repo (For large organizations):
infrastructure-gitops/ # Infrastructure teamapp-team-1-gitops/ # App team 1app-team-2-gitops/ # App team 22. Environment Promotion
Git Branches Approach:
main → productiondevelop → stagingfeature/* → devDirectory Approach (Recommended):
apps/├── base/└── overlays/ ├── dev/ ├── staging/ └── production/Git Tags for Production:
# Promote to production by tagginggit tag -a v1.2.3 -m "Release v1.2.3"git push origin v1.2.3
# ArgoCD/Flux watches specific tag patterntargetRevision: v*3. Secret Management
Sealed Secrets (Bitnami):
# Install sealed-secrets controllerkubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Encrypt secretkubectl create secret generic db-creds \ --from-literal=username=admin \ --from-literal=password=secret \ --dry-run=client -o yaml | \ kubeseal -o yaml > sealed-secret.yaml
# Commit sealed-secret.yaml to GitSOPS (Mozilla):
creation_rules: - path_regex: .*.yaml encrypted_regex: ^(data|stringData)$ kms: arn:aws:kms:us-east-1:ACCOUNT:key/KEY-ID# Encrypt secretsops -e secret.yaml > secret.enc.yaml
# Flux automatically decryptsapiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: appsspec: decryption: provider: sops4. Monitoring and Alerting
ArgoCD Notifications:
# argocd-notifications-cm ConfigMapapiVersion: v1kind: ConfigMapmetadata: name: argocd-notifications-cm namespace: argocddata: service.slack: | token: $slack-token template.app-deployed: | message: | Application {{.app.metadata.name}} is now running version {{.app.status.sync.revision}}. trigger.on-deployed: | - when: app.status.operationState.phase in ['Succeeded'] send: [app-deployed]Flux Alerts:
apiVersion: notification.toolkit.fluxcd.io/v1beta2kind: Providermetadata: name: slack namespace: flux-systemspec: type: slack channel: gitops-notifications secretRef: name: slack-webhook-url---apiVersion: notification.toolkit.fluxcd.io/v1beta2kind: Alertmetadata: name: production-alerts namespace: flux-systemspec: providerRef: name: slack eventSeverity: info eventSources: - kind: Kustomization name: apps - kind: HelmRelease name: '*'Choosing Between ArgoCD and Flux
Choose ArgoCD If:
- You want a powerful web UI
- Multi-tenancy is critical (teams, projects)
- You need strong RBAC and SSO integration
- You prefer a batteries-included solution
- You want easier onboarding for teams new to GitOps
Choose Flux If:
- You prefer a modular, composable architecture
- You need deep Helm integration
- Image automation is a primary use case
- You want CNCF-native integrations
- You’re comfortable with CLI-first workflows
Use Both If:
- Large organization with different team needs
- Migrate gradually from one to another
- Different clusters with different requirements
Production Checklist
- Git repository structure defined
- RBAC and access controls configured
- Secret management solution implemented
- Monitoring and alerting configured
- Disaster recovery plan documented
- Environment promotion strategy defined
- Progressive delivery enabled (Rollouts/Flagger)
- Image update automation configured
- Multi-cluster management strategy
- Backup and restore procedures tested
Conclusion
GitOps transforms Kubernetes deployments by leveraging Git’s strengths: version control, peer review, and audit trails. Both ArgoCD and Flux are production-ready tools with strong communities and active development.
Start with the tool that matches your team’s needs, implement automated syncing, and gradually adopt advanced features like progressive delivery and multi-cluster management. GitOps will fundamentally improve your deployment confidence, velocity, and reliability.
Ready to implement GitOps? Our DevOps training programs cover GitOps fundamentals, ArgoCD, Flux, and advanced deployment strategies with hands-on labs. Explore GitOps training or schedule a consultation to modernize your deployment pipeline.