← All articles
DEVOPS GitOps and Continuous Deployment with ArgoCD and Flux 2026-02-09 · 8 min read · gitops · argocd · flux

GitOps and Continuous Deployment with ArgoCD and Flux

DevOps 2026-02-09 · 8 min read gitops argocd flux kubernetes cd deployment

GitOps and Continuous Deployment with ArgoCD and Flux

GitOps is one of those ideas that sounds simple -- "use Git as the source of truth for infrastructure" -- but has real depth once you start implementing it. Done well, it gives you auditable, repeatable deployments with built-in rollback. Done poorly, it adds a layer of indirection that slows everything down.

This guide covers what GitOps actually means in practice, compares the two dominant tools (ArgoCD and Flux), and gives honest guidance on when it's worth adopting.

What GitOps Actually Means

GitOps has a specific definition beyond "we store YAML in Git." The core principles:

  1. Declarative configuration. The entire desired state of your system is described declaratively (Kubernetes manifests, Helm charts, Kustomize overlays).
  2. Versioned and immutable. That desired state lives in Git, giving you a full audit trail and the ability to roll back to any previous state.
  3. Pulled automatically. A software agent in the cluster continuously reconciles the actual state with the desired state from Git. You push to Git; the agent deploys.
  4. Continuously reconciled. If someone manually changes the cluster (kubectl edit, dashboard changes), the agent detects the drift and reverts it.

The key difference from traditional CI/CD: in a traditional pipeline, the CI system pushes changes to the cluster. In GitOps, the cluster pulls changes from Git. This distinction matters for security -- the cluster needs read access to Git, not the other way around. Your CI system never needs cluster credentials.

GitOps vs Traditional CI/CD

Traditional CI/CD:
  Developer -> Git Push -> CI Build -> CI Deploy -> Cluster
  (CI needs cluster credentials)

GitOps:
  Developer -> Git Push -> CI Build -> Push manifest to Git
  Agent in Cluster -> Watches Git -> Applies changes -> Cluster
  (Cluster needs Git read access only)

The security improvement is significant. In traditional CI/CD, a compromised CI runner can deploy arbitrary code to production. In GitOps, a compromised CI runner can only modify a Git repository, and any changes are visible, reviewable, and revertible.

ArgoCD

ArgoCD is the most popular GitOps tool. It's a CNCF graduated project with a polished web UI, granular RBAC, and support for Helm, Kustomize, Jsonnet, and plain YAML.

Installation

# Create namespace and install
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=120s

# Get initial admin password
argocd admin initial-password -n argocd

# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Login via CLI
argocd login localhost:8080 --insecure

Creating an Application

ArgoCD applications define the mapping between a Git repository and a Kubernetes cluster/namespace:

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myapp-deploy.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp
  syncPolicy:
    automated:
      prune: true        # Delete resources removed from Git
      selfHeal: true     # Revert manual cluster changes
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
kubectl apply -f application.yaml

With automated.selfHeal: true, if someone runs kubectl edit to change a deployment, ArgoCD will detect the drift and revert it within seconds. This is GitOps enforcement -- the Git repository is the single source of truth.

Application Sets

For managing many applications (microservices, multi-environment), ApplicationSets generate Application resources from templates:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
  namespace: argocd
spec:
  generators:
    - git:
        repoURL: https://github.com/myorg/deploy.git
        revision: main
        directories:
          - path: "apps/*"
  template:
    metadata:
      name: "{{path.basename}}"
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/deploy.git
        targetRevision: main
        path: "{{path}}"
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{path.basename}}"

This creates one ArgoCD Application for every directory under apps/ in your deploy repo. Add a new service by adding a directory -- no ArgoCD configuration changes needed.

ArgoCD Strengths

Flux

Flux (v2) is the other major GitOps tool, also a CNCF graduated project. Where ArgoCD takes an application-centric approach with a UI, Flux takes a Kubernetes-native approach -- everything is a Custom Resource, and there is no built-in UI.

Installation

# Install Flux CLI
brew install fluxcd/tap/flux    # macOS
curl -s https://fluxcd.io/install.sh | bash   # Linux

# Bootstrap Flux into your cluster (creates repo structure)
flux bootstrap github \
  --owner=myorg \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/production \
  --personal

The bootstrap command does several things: installs Flux controllers into the cluster, creates the Git repository if it doesn't exist, and commits the Flux configuration to the repository. From this point, Flux manages itself through GitOps -- including its own upgrades.

Defining Sources and Kustomizations

Flux separates "where to get manifests" (Sources) from "how to apply them" (Kustomizations):

# source.yaml - GitRepository source
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: myapp
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/myorg/myapp-deploy.git
  ref:
    branch: main
  secretRef:
    name: myapp-git-credentials
---
# kustomization.yaml - What to deploy from the source
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: myapp
  namespace: flux-system
spec:
  interval: 5m
  targetNamespace: myapp
  sourceRef:
    kind: GitRepository
    name: myapp
  path: ./overlays/production
  prune: true
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: myapp
      namespace: myapp
  timeout: 3m

Flux with Helm

Flux has first-class Helm support through HelmRepository and HelmRelease resources:

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 1h
  url: https://kubernetes.github.io/ingress-nginx
---
apiVersion: helm.toolkit.fluxcd.io/v2
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: 2
      metrics:
        enabled: true

Flux Strengths

ArgoCD vs Flux: Honest Comparison

Aspect ArgoCD Flux
UI Built-in, polished None (use Weave GitOps or Capacitor)
Architecture Centralized server Distributed controllers
Multi-cluster Native (managed centrally) Per-cluster install (use Flux with remote sources)
Helm support Renders in cluster HelmRelease CRD (more Kubernetes-native)
Image automation Separate ArgoCD Image Updater Built-in (Image Reflector + Automation controllers)
RBAC Built-in, project-based Delegates to Kubernetes RBAC
Learning curve Moderate (UI helps) Steeper (CRD-heavy, no UI)
Resource usage Higher (UI, API server, repo server) Lower (lightweight controllers)
Community Larger (more stars, more contributors) Smaller but active

Choose ArgoCD when: You want a UI for visibility, manage multiple clusters centrally, or have teams that prefer visual tools over CLI-only workflows.

Choose Flux when: You want a lightweight, Kubernetes-native approach, need image automation built-in, or prefer managing everything through YAML and kubectl.

Both tools are CNCF graduated and production-ready. The choice is more about workflow preference than capability.

Handling Secrets in GitOps

The hardest problem in GitOps is secrets. You cannot commit plaintext secrets to Git, but your applications need them. There are several approaches:

Sealed Secrets

Bitnami's Sealed Secrets encrypts secrets with a cluster-side key. Only the cluster can decrypt them.

# Install
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.0/controller.yaml

# Encrypt a secret
kubectl create secret generic myapp-db \
  --from-literal=password=supersecret \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-secret.yaml

The encrypted SealedSecret resource is safe to commit to Git. The controller in the cluster decrypts it into a regular Kubernetes Secret.

SOPS (Mozilla)

SOPS encrypts values within YAML files, leaving keys readable. Works with AWS KMS, GCP KMS, Azure Key Vault, and age.

# Encrypt
sops --encrypt --age age1... secret.yaml > secret.enc.yaml

# Flux decrypts automatically with SOPS provider

Flux has native SOPS integration -- add a decryption field to your Kustomization and Flux decrypts on the fly.

External Secrets Operator

External Secrets Operator syncs secrets from external stores (AWS Secrets Manager, HashiCorp Vault, 1Password) into Kubernetes Secrets:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-db
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: myapp-db
  data:
    - secretKey: password
      remoteRef:
        key: production/myapp/database
        property: password

This is the most operationally sound approach for teams already using a secrets manager. The secret values never touch Git.

Rollback Strategies

Git Revert

The simplest rollback in GitOps: revert the commit that introduced the bad change.

git revert HEAD
git push origin main

The GitOps agent detects the new commit and applies the previous state. This is the canonical GitOps rollback -- it creates an audit trail and is the same process as any other change.

ArgoCD Rollback

ArgoCD keeps a history of synced revisions:

# List sync history
argocd app history myapp

# Rollback to a previous revision
argocd app rollback myapp <revision-number>

Note that this is a temporary fix -- ArgoCD will eventually re-sync from Git. For a permanent rollback, revert in Git.

Flux Rollback

Flux with Helm supports automatic rollback on failed deployments:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
spec:
  upgrade:
    remediation:
      remediateLastFailure: true
      retries: 3
  rollback:
    cleanupOnFail: true
    recreate: true

If a Helm upgrade fails health checks, Flux automatically rolls back to the last successful release.

When GitOps Is Overkill

GitOps adds real complexity: a separate deployment repository (or directory), a reconciliation agent, secrets management, and a new mental model. It is not always worth it.

GitOps is overkill when:

GitOps is worth it when:

The honest truth: if you're not on Kubernetes, GitOps tools like ArgoCD and Flux don't apply. And if you are on Kubernetes but deploy one or two services, a simple GitHub Actions workflow that runs kubectl apply is fine. GitOps shines at scale -- many services, many environments, many teams.

Getting Started

If you're adopting GitOps for the first time:

  1. Start with one non-critical service. Don't migrate your entire platform at once.
  2. Use a separate deployment repo (or a clearly separated directory in your mono-repo). Keep application code and deployment manifests on different commit cadences.
  3. Choose Sealed Secrets or SOPS for secrets initially. External Secrets Operator is better long-term but adds another component to manage.
  4. Enable automated sync with self-heal from the start. Manual sync mode defeats the purpose of GitOps.
  5. Set up notifications. Both ArgoCD and Flux support Slack/webhook alerts. You need to know when syncs fail.

GitOps is a workflow, not a product. The tools enforce the workflow, but the real value comes from treating Git as the single source of truth for your infrastructure state. Once that mental model clicks, deployments become predictable and auditable -- and that is worth the setup cost.