Skip to content
Vladimir Chavkov
Go back

GitOps and Continuous Delivery: ArgoCD vs Flux for Kubernetes

Edit page

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

  1. Declarative: Entire system described declaratively in Git
  2. Versioned and Immutable: Git commit history provides version control
  3. Pulled Automatically: Software agents pull desired state from Git
  4. 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 syncs

Benefits of GitOps

ArgoCD vs Flux: Feature Comparison

FeatureArgoCDFlux
UIRich web UI with visualizationsNo native UI (use Weave GitOps UI)
Multi-TenancyStrong (Projects, RBAC, SSO)Good (with Flux multi-tenancy)
Helm SupportNative, excellentNative via Helm Controller
Kustomize SupportNativeNative
Image AutomationVia Argo CD Image UpdaterNative via Image Automation Controller
Progressive DeliveryVia Argo RolloutsVia Flagger
NotificationsBuilt-in (Slack, Teams, etc.)Via Notification Controller
CNCF StatusGraduatedGraduated
ArchitectureMonolithic (easier to start)Modular (flexible but complex)
ConfigurationCRD-based (Application)CRD-based (Kustomization, HelmRelease)
Multi-ClusterExcellent supportGood support

Implementing GitOps with ArgoCD

1. Installation

Terminal window
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Expose ArgoCD server
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Login with CLI
argocd login localhost:8080
argocd account update-password

2. 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:

apps/production/web-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app
namespace: argocd
# Finalizer ensures cascading delete
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
# 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 replicas

Apply the application:

Terminal window
kubectl apply -f apps/production/web-app.yaml
# Or via CLI
argocd 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-heal

4. Multi-Cluster Management

# Add external cluster
# First, get cluster credentials
argocd cluster add production-cluster --name production
# Create application targeting external cluster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app-external
namespace: argocd
spec:
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: true

5. App of Apps Pattern

Manage multiple applications as a single unit:

clusters/production/apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-apps
namespace: argocd
spec:
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: true
clusters/production/applications/web-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web-app
namespace: argocd
spec:
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: true

6. Progressive Delivery with Argo Rollouts

apps/base/rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: web-app
spec:
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: 8080

Implementing GitOps with Flux

1. Installation

Terminal window
# Install Flux CLI
curl -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 repo

2. 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

clusters/production/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
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: production

4. Helm Release Management

infrastructure/sources/helm-repositories.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: ingress-nginx
namespace: flux-system
spec:
interval: 24h
url: https://kubernetes.github.io/ingress-nginx
---
# infrastructure/ingress-nginx/release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
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: true

5. Image Automation

apps/production/image-repository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: web-app
namespace: flux-system
spec:
image: ghcr.io/company/web-app
interval: 1m
---
# apps/production/image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: web-app
namespace: flux-system
spec:
imageRepositoryRef:
name: web-app
policy:
semver:
range: ">=1.0.0"
---
# apps/production/image-update.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: web-app
namespace: flux-system
spec:
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: Setters

Add image policy marker in deployment:

apps/production/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
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 Flagger
flux 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 deployment
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: web-app
namespace: production
spec:
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 configs

Multi-repo (For large organizations):

infrastructure-gitops/ # Infrastructure team
app-team-1-gitops/ # App team 1
app-team-2-gitops/ # App team 2

2. Environment Promotion

Git Branches Approach:

main → production
develop → staging
feature/* → dev

Directory Approach (Recommended):

apps/
├── base/
└── overlays/
├── dev/
├── staging/
└── production/

Git Tags for Production:

Terminal window
# Promote to production by tagging
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3
# ArgoCD/Flux watches specific tag pattern
targetRevision: v*

3. Secret Management

Sealed Secrets (Bitnami):

Terminal window
# Install sealed-secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Encrypt secret
kubectl 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 Git

SOPS (Mozilla):

.sops.yaml
creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
kms: arn:aws:kms:us-east-1:ACCOUNT:key/KEY-ID
Terminal window
# Encrypt secret
sops -e secret.yaml > secret.enc.yaml
# Flux automatically decrypts
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
spec:
decryption:
provider: sops

4. Monitoring and Alerting

ArgoCD Notifications:

# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
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/v1beta2
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: gitops-notifications
secretRef:
name: slack-webhook-url
---
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: production-alerts
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: apps
- kind: HelmRelease
name: '*'

Choosing Between ArgoCD and Flux

Choose ArgoCD If:

Choose Flux If:

Use Both If:

Production Checklist

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.


Edit page
Share this post on:

Previous Post
DevSecOps: Integrating Security Automation into CI/CD Pipelines
Next Post
BigQuery for Data Engineering: Advanced Analytics and Performance Optimization