GitOps in Practice: Moving Beyond CI/CD to Continuous Delivery
← Back to blogDevOps

GitOps in Practice: Moving Beyond CI/CD to Continuous Delivery

J
Jason Miller
· 7 min read

CI/CD pipelines revolutionized software delivery. Automated testing, one-click deployments, consistent builds — it changed how teams ship. But as systems have grown more complex, a fundamental problem with traditional pipelines has become harder to ignore: they push, and then forget.

GitOps is a different model. Instead of a pipeline that runs a deployment command and moves on, GitOps uses a reconciliation loop that continuously compares what's declared in Git with what's running in your cluster — and corrects any drift.

Push vs pull: the key difference

In a push-based CI/CD system, your pipeline:

  1. Builds the artifact
  2. Runs tests
  3. Pushes a kubectl apply or Helm upgrade to the cluster
  4. Moves on

The problem: after step 4, the pipeline has no idea what's actually running. Someone could kubectl edit a deployment directly, a node restart could cause a rollback, a misconfigured resource could drift from the desired state. Your CI/CD system is none the wiser.

In a pull-based GitOps model:

  1. A developer commits a change to the desired state in Git (a Kubernetes manifest, a Helm values file)
  2. A controller running inside the cluster detects the difference between Git and the live cluster
  3. The controller applies the change automatically

The controller is always watching. Drift is corrected automatically. The cluster self-heals toward whatever is declared in Git.

Why this matters in practice

Credential security

In a push model, your CI system needs credentials to reach your cluster. That means storing cluster admin credentials in your CI provider — a significant blast radius if those credentials leak.

In a pull model, the controller runs inside the cluster. Nothing outside the cluster needs deployment credentials. Your CI pipeline only needs access to the Git repo and container registry.

Audit trail

Because every change to cluster state goes through a Git commit, you get a complete, tamper-evident audit trail for free. Who changed what, when, and why — it's all in Git history. This matters for compliance and it matters at 2am during an incident.

Drift detection

Manual cluster changes (emergency hotfixes, debugging sessions that mutate state, misapplied kubectl commands) are detected and corrected automatically. This is both a safety net and occasionally a source of surprise for engineers who expect manual changes to stick.

ArgoCD vs Flux

The two dominant GitOps tools are ArgoCD and Flux. Both implement the reconciliation loop — they differ in philosophy and UX.

ArgoCD gives you a rich UI, application-centric abstractions, and a declarative Application CRD. It's more opinionated and easier to get started with, especially if your team is used to thinking in "applications" rather than raw Kubernetes resources.

Flux is more modular. It's a collection of controllers (source controller, kustomize controller, helm controller) that can be composed. Teams that want fine-grained control or already have strong Kustomize opinions often prefer Flux.

For teams getting started: ArgoCD. For teams with complex existing tooling or who want the Flux composability model: Flux.

Structuring your config repo

How you organize your GitOps config repo matters. A common pattern:

infra/
  base/                  # shared manifests, applied to all envs
  overlays/
    staging/             # kustomize overlays for staging
    production/          # kustomize overlays for production
apps/
  api/
    base/
    overlays/
  worker/
    ...

The base directory contains the canonical resource definitions. Overlays use Kustomize to patch environment-specific values (replica counts, resource limits, image tags).

Handling secrets

Secrets are the one thing you don't want in Git as plaintext. Two common approaches:

Sealed Secrets (Bitnami): Encrypt secrets using a public key before committing. The controller in the cluster decrypts them using the private key. Simple, works well for smaller teams.

External Secrets Operator: Syncs secrets from an external store (AWS Secrets Manager, GCP Secret Manager, Vault) into Kubernetes. Adds an external dependency but keeps secrets out of Git entirely — even encrypted.

For new projects, we recommend External Secrets Operator if you're already using a cloud secret manager. Sealed Secrets if you want to keep everything in-cluster.

The migration path

If you're moving from push-based CI/CD to GitOps, the transition doesn't have to be big-bang:

  1. Start with non-production environments — get ArgoCD or Flux watching your staging cluster first
  2. Migrate one application — pick something low-risk and learn how the reconciliation model works in practice
  3. Handle secrets first — before migrating production, get your secrets management pattern solid
  4. Define your repo structure — agree on conventions before you have 20 apps in your config repo
  5. Migrate production — once the model is proven in staging, move production environments

GitOps solves a class of problems that push-based pipelines fundamentally cannot: drift, credential exposure, and the loss of ground truth that happens when your cluster state diverges from what any pipeline last applied.

It's not right for every team at every scale. But for teams operating Kubernetes at any meaningful complexity, the reconciliation model is almost always worth the transition cost.

We help teams design and implement GitOps workflows — from repo structure through ArgoCD/Flux deployment. Reach out to talk through your setup.

Working on something similar?

We help engineering teams implement the practices covered in this post. First call is free.

Start a conversation →