By DevOps Enginer – Cloud-Native Series · Updated for 2026
Quick answer: Traditional CI/CD pushes a deployment to your environment from a pipeline. GitOps pulls the desired state from Git using an operator running in (or managing) the environment, which continuously reconciles reality with Git. GitOps does not replace CI — most GitOps teams still use a CI pipeline to build, test, and publish an image, then let GitOps handle deployment. If you run Kubernetes across multiple services or clusters and care about drift, audit, and repeatable rollbacks, GitOps is usually the stronger deployment model. If you deploy a simple app, serverless functions, or VMs, a traditional pipeline is often simpler and perfectly appropriate.
I learned why this matters on my own 3D portfolio project: after rounds of manual fixes and re-uploads, what was live no longer matched what I thought I had last deployed, and several errors only surfaced later.
Originally published on DevOps Enginer.
Table of contents
- What traditional CI/CD is
- What GitOps is (and what it is not)
- Side-by-side comparison
- The 4 differences that actually matter
- Worked example 1: a traditional Kubernetes pipeline
- Worked example 2: the hybrid model most teams actually use (CI + GitOps)
- Rollbacks, drift, and promotion between environments
- Security and secrets — honestly
- When traditional CI/CD is the better choice
- When GitOps is the better choice
- A practical migration roadmap
- Common mistakes
- Decision checklist
- FAQ
- Conclusion
1. What traditional CI/CD is
Traditional CI/CD automates the path from code change to running software:
- A developer pushes code.
- The CI server checks out the code and runs tests.
- The pipeline builds an artifact — usually a container image — and pushes it to a registry.
- The CD stage then pushes the deployment out: it connects to the target (a server, a platform, or a Kubernetes cluster) with credentials it holds, and applies the change.
- After that apply, the pipeline normally stops watching. If something changes in the environment afterwards, the pipeline does not know until the next run.
Popular CI/CD tools include GitHub Actions, GitLab CI, Jenkins, CircleCI, and Bitbucket Pipelines.
Strengths
- Simple mental model and quick to set up for a first deployment.
- Very flexible: the pipeline is a script, so it can deploy almost anywhere — VMs, serverless, Kubernetes, a single VPS.
- Huge ecosystems of plugins, examples, and community answers.
- Works just as well for a monolith as for microservices.
Weaknesses
- The pipeline usually holds powerful, long-lived credentials for production.
- The environment can drift away from what the pipeline last applied, silently.
- "What is in production?" is answered by pipeline logs, not by a single declarative source of truth.
- Rollback often means re-running an old pipeline or re-applying old manifests by hand.
- Every team invents its own promotion, approval, and rollback conventions.
2. What GitOps is (and what it is not)
GitOps treats a Git repository as the declarative source of truth for the desired state of an application or environment.
- The desired state — Kubernetes manifests, Helm values, or Kustomize overlays — lives in Git.
- A GitOps operator (most commonly Argo CD or Flux) watches that repository.
- On its next reconciliation, the operator compares the live environment with Git.
- If they differ, it syncs the environment towards Git — or reports the drift, depending on its sync settings.
- To change production, you change Git — normally through a pull request. To roll back, you revert that change in Git.
Popular GitOps tools include Argo CD, Flux, and Fleet.
GitOps is not:
- A replacement for CI. Something still has to test code, build the image, scan it, and publish it. GitOps starts after there is an artifact to deploy.
- "Just YAML in Git." The discipline — reviews, branch protection, automated reconciliation, and no manual changes to production — is the point, not the file format.
- Kubernetes-only in principle, but Kubernetes-first in practice. It fits declarative systems best, and Kubernetes is the most common one. It is a poor fit for purely imperative targets.
- Magic for databases or one-off jobs. A GitOps operator can deploy a migration job, but schema migrations, data backfills, and ordering still need deliberate design — they are not made safe just by being declared in Git.
Strengths
- Every production change is a Git commit: who, what, when, and (in the PR) why.
- Environments are reproducible from Git — rebuilding a cluster or recreating an environment starts from the same source.
- Continuous reconciliation gives you drift detection, and optionally drift correction.
- The deployment credentials model can be tighter: the operator runs with access to its clusters, and external CI no longer needs standing production access.
- Rollback is a familiar, reviewable operation:
git revert.
Weaknesses
- Higher learning curve: your team must be comfortable with Git discipline, manifests/Helm/Kustomize, and debugging the operator itself.
- Git becomes a critical, sensitive system. If anyone can commit unreviewed changes straight to the deployment branch, you have made security worse, not better.
- Adds an operator to run, upgrade, monitor, and secure.
- Feedback can feel less direct at first — a failed sync is a different debugging experience from a failed pipeline step.
- Not ideal for legacy/monolith estates that are not declarative.
3. Side-by-side comparison
| Category | Traditional CI/CD | GitOps |
|---|---|---|
| Deployment method | Pipeline pushes to the target | Operator pulls desired state from Git |
| Source of truth | Pipeline definition + last applied state | Git repository (desired state) |
| Trigger | Pipeline run | Git change + continuous reconciliation |
| Who holds production access? | The CI/CD system | The GitOps operator (CI usually does not) |
| Drift detection | Not built in | Built in; correction depends on sync policy |
| Rollback | Re-run old pipeline / re-apply old version |
git revert the deployment change |
| Audit trail | Pipeline logs (app code is in Git, deployment state may not be) | Git history for deployment changes |
| Environment promotion | Re-run the pipeline per environment, often with different variables | Promote the same declarative state through overlays/directories per environment |
| Secrets | Pipeline secrets / variables | Must be solved separately — never plain secrets in Git (see Security section) |
| Testing | Unit, integration, E2E, scans in the pipeline | Same CI tests, plus manifest validation and policy checks before merge |
| Failure you see | A red pipeline step | An app that is OutOfSync / degraded in the operator |
| Day-2 question it answers best | "Did the release run?" | "Does production still match what we said it should be?" |
| Best fit | Simple apps, VMs, serverless, mixed/non-declarative targets | Kubernetes, microservices, multi-cluster, regulated/audited environments |
| Main cost | Credential sprawl and drift over time | Up-front discipline and operating the operator |
4. The 4 differences that actually matter
4.1 Drift detection — and self-healing
If someone changes production directly — scales a deployment by hand during an incident, edits a resource with kubectl, hot-fixes a config value — a traditional pipeline will not notice. The change lives until the next deployment overwrites it, possibly without anyone realising it was there.
A GitOps operator continuously compares live state with Git, so that change shows up as drift. With automated sync and self-healing enabled, the operator will revert it on reconciliation. With manual sync, it will report it and wait for a human decision — which is often what you want for production.
That is drift in miniature: small manual changes, no single source of truth, and a cleanup where you cannot fully reconstruct what changed. A GitOps operator would have flagged that difference immediately.
This reconciliation loop is also the mechanism behind how self-healing infrastructure works in Kubernetes.
Be honest about the trade-off: self-healing will also fight a legitimate emergency change. Mature teams define a break-glass procedure in advance — for example, who may pause automated sync during an incident, and how the emergency change gets written back into Git afterwards.
4.2 Reproducible, declarative deployments
In GitOps, the manifests, Helm values, and Kustomize overlays that describe an environment are versioned together. Recreating the environment means pointing an operator at the same repository — not reconstructing what a series of pipeline runs happened to do over six months.
This is also what makes disaster recovery and cluster migration calmer: Git is your rebuild plan, provided you also back up the things GitOps does not store — secrets (stored in your secrets system), persistent data, and the operator's own configuration.
4.3 Auditing and approvals move into Git
A pull request to the deployment repository can carry the whole change story: the diff, the reviewer, the discussion, and the linked ticket. Branch protection, required reviewers, and required status checks become your deployment approval gate.
Auditors still need more than Git — they will ask about access to Git itself, to the image registry, and to the cluster — but "show me every change to production last month, and who approved it" becomes a much shorter conversation.
4.4 A different security model — not an automatically safer one
In the push model, your CI system is a high-value target: compromise the pipeline or its production credential and you can deploy anything.
In the pull model, external CI does not need standing production access, which removes one large credential. In exchange, write access to the deployment repository becomes production access, and the operator itself runs with powerful cluster permissions.
So GitOps changes where you must be careful. It does not remove the need to be careful. See the Security section below.
5. Worked example 1: a traditional Kubernetes pipeline
The flow:
Developer → CI pipeline → Build & test → Push image → Pipeline deploys to production → (pipeline stops watching)
A simplified GitHub Actions example. Note the last job: the pipeline itself connects to the cluster and applies the manifests — that is the push model.
# .github/workflows/deploy-traditional.yml
name: Deploy to Kubernetes (traditional push)
on:
push:
branches: ["main"]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.meta.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Build and push image
id: meta
run: |
IMAGE="ghcr.io/org/myapp:${{ github.sha }}"
docker build -t "$IMAGE" .
# A registry login step (with a short-lived / scoped token) goes here.
docker push "$IMAGE"
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
deploy:
needs: build
runs-on: ubuntu-latest
environment: production # Use environment protection rules for a manual approval, if you need one.
steps:
- uses: actions/checkout@v4
# Configure kubectl access for YOUR cluster here, preferably with
# short-lived, cloud-issued credentials (e.g. OIDC federation) —
# NOT a long-lived kubeconfig stored as a plain repository secret.
- name: Apply manifests
run: |
kubectl apply -f k8s/
kubectl rollout status deployment/myapp
The structural issue to point out to readers: whatever credential that deploy job uses can change production. Protect it like production access — because it is production access.
6. Worked example 2: the hybrid model most teams actually use (CI + GitOps)
In practice, the choice is rarely "CI/CD or GitOps". It is usually:
Developer → CI builds, tests, scans & publishes image
→ CI (or a human PR) updates the image tag in the config repo
→ GitOps operator detects the Git change
→ Operator syncs the cluster and keeps reconciling it
CI does what CI is good at. GitOps does deployment and day-2 reconciliation.
6.1 Repository layout with Kustomize overlays
One common layout keeps one config repository with a base and one overlay per environment:
myapp-config/
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml
staging/
kustomization.yaml
prod/
kustomization.yaml
Preview exactly what will be applied before you commit it:
kustomize build overlays/prod
And the step CI typically performs after publishing a new image — updating the image reference in Git, as a commit (or better, as a pull request for production):
cd overlays/prod
kustomize edit set image myapp=ghcr.io/org/myapp:REPLACE_WITH_IMAGE_SHA
That Git commit — reviewed like any other production change — is the deployment request. There is no separate "deploy" job holding cluster credentials.
6.2 Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/myapp-config
targetRevision: main
path: overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: prod
syncPolicy:
automated:
prune: true # Remove resources that are no longer in Git — understand this before enabling it
selfHeal: true # Revert drift from Git — see the break-glass warning in section 4.1
syncOptions:
- CreateNamespace=true
Argo CD runs inside (or with access to) the cluster and pulls changes from Git. No pipeline needs a standing credential for that cluster.
6.3 The Flux alternative (same idea, different operator)
If you prefer Flux, the equivalent wiring is a source plus a reconciliation object:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: myapp-config
namespace: flux-system
spec:
interval: 1m
url: https://github.com/org/myapp-config
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp-prod
namespace: flux-system
spec:
interval: 5m
path: ./overlays/prod
prune: true
sourceRef:
kind: GitRepository
name: myapp-config
targetNamespace: prod
Argo CD or Flux? Both implement the same model. Teams often choose Argo CD for its UI and application-centric view, and Flux for its GitOps-toolkit/Git-native, composable approach. Evaluate both against your platform team's skills rather than treating either as the "winner" — and say so in the post. Readers trust a comparison that does not oversell.
7. Rollbacks, drift, and promotion between environments
Rollback
Traditional: re-run the pipeline at the previous commit, or re-apply the previous manifests/image tag by hand. Workable, but the steps and permissions differ per team and per pipeline.
GitOps:
# Find the bad deployment change in the config repo, then revert it:
git log --oneline # identify the deployment commit
git revert <commit-sha> # creates a new commit that undoes it — reviewable, auditable
git push
# Optional: inspect sync/history from the Argo CD CLI
argocd app history myapp-prod
argocd app get myapp-prod
The operator sees the revert, and reconciles the cluster back to the previous desired state. Your rollback is a Git operation your whole team already knows how to review.
I don't keep exact rollback timings, and I won't invent them. The difference I care about is procedural: with GitOps, rollback is a revert I can review; with a push pipeline it usually means re-applying an older version and hoping the environment still matches it.
Promotion: dev → staging → prod
Avoid promoting by re-running different pipelines with different hand-set variables. Prefer promoting the same artifact and the same base manifests, changing only the overlay for each environment (image tag, replicas, resources, config). That way "it worked in staging" means something close to "the same thing is now in prod, with prod settings".
A common recommendation is separate directories/overlays per environment rather than long-lived environment branches, because branches tend to diverge and make promotion merges painful. Present this as a recommendation with that reasoning, not as a rule — plenty of teams use branches successfully.
8. Security and secrets — honestly
| Security question | Traditional CI/CD | GitOps |
|---|---|---|
| Does the CI system need production access? | Yes, in a push model | Usually no standing cluster access for CI |
| What is the highest-value target? | Pipeline + its production credentials | Deployment repo write access + the operator |
| Is every production change reviewed? | Only if you build that into the pipeline/process | Can be enforced with protected branches + required reviews |
| Drift detection? | Not built in | Built in; auto-correction is configurable |
| Where do secrets live? | Pipeline/CI secrets, or a secrets manager the pipeline reads | A secrets system — never plain text in Git |
Secrets rule that applies to both models: do not commit plain-text secrets, kubeconfigs, or registry passwords to Git — including the config repository. Common patterns are an external-secrets integration that syncs from a cloud secret manager / Vault into Kubernetes Secrets, or encrypted-in-Git approaches such as Sealed Secrets where only the cluster can decrypt. Either pattern works; what matters is picking one, documenting it, and using it consistently across every service.
Policy as a pre-merge gate: manifest validation and policy checks (for example with OPA or Kyverno) can run in CI on the config repo, so a manifest that requests a privileged container, a missing resource limit, or an image from an unapproved registry fails before it is merged — not after it reaches the cluster. Keep any policy YAML you publish minimal and tested; do not copy a policy example you have not run.
9. When traditional CI/CD is the better choice
- You deploy serverless (for example Lambda or Cloud Run) or other non-Kubernetes targets where a declarative cluster operator adds little.
- You ship a monolith on VMs / a simple container platform and the pipeline is already reliable and understood.
- You are a small team with a simple app — the fastest safe path is a good pipeline, good tests, and a documented rollback.
- You are prototyping rapidly and the deployment shape changes every week.
- Your estate is mostly non-declarative ( imperative installers, manual vendor appliances, snowflake servers). GitOps will not fix that estate first; automation and standardisation come before reconciliation.
If you are weighing the serverless route instead, see Serverless vs Containers: how to choose the right architecture.
10. When GitOps is the better choice
- You run Kubernetes (EKS, GKE, AKS, or self-managed) as a serious production platform.
- You have microservices that share deployment patterns.
- You run multiple clusters or environments that must stay consistent.
- You work in a regulated or audited setting where "who approved this production change?" must be answerable.
- You are building a platform engineering capability and want development teams to self-serve deployments through Git, inside guardrails the platform team owns.
- Drift and "works on staging, differs in prod" incidents are a recurring source of outages for you.
11. A practical migration roadmap
Do not migrate everything at once. A low-risk path:
- Keep CI. Add GitOps for deployment only. Pick one non-critical service on one cluster. CI keeps building and publishing images exactly as today.
- Create the config repository properly from day one: protected branch, required reviewers, required status checks (manifest lint/validation), no direct pushes to the deployment branch.
- Start with manual sync, then automate. Watch the operator report drift for a while so the team learns its signals. Enable automated sync, then (later, deliberately) prune and self-heal.
- Solve secrets before you scale. One documented pattern, used by every service — not a different improvisation per team.
- Write the break-glass and rollback playbooks. How do we pause sync in an incident? How do we revert? Who can approve an emergency change, and how is it written back to Git?
- Expand by pattern, not by heroics. Once the pilot is boring, templatise it (Helm chart or Kustomize base + overlays) and onboard services in small batches.
My hands-on GitOps experience is with Argo CD rather than Flux, so the examples here lean that way. If you pilot this, start with one low-risk service, watch a few syncs and one deliberate rollback, and only expand when the process feels boring.
12. Common mistakes
- Letting anyone push straight to the deployment branch. This removes your review gate and makes Git a back door to production.
- Storing secrets in the config repo because "it's a private repo". Private is not encrypted, and repo access tends to spread over time.
- Turning on prune + self-heal on day one before you understand what the operator considers drift, and without a break-glass procedure.
- Putting application source and deployment config through the same release cadence without thinking. Many teams separate the app repo from the config repo so an image promotion is a small, reviewable config change — but a monorepo can also work if branch protection is per-path. Present the trade-off; do not declare one layout universally correct.
- Treating manifests as "set and forget". Resource requests/limits, probes, and autoscaling settings still need the same care they needed with a pipeline — GitOps changes how they are delivered, not whether they are right.
- Migrating the hardest service first to "prove it works". Prove it on an easy service; build confidence and templates; then take on the hard one.
13. Decision checklist
Choose traditional CI/CD (for now) if most of these are true:
- ☐ Little or no production Kubernetes
- ☐ One simple app / one environment
- ☐ Small team, low change frequency
- ☐ No audit or drift problem you are actually feeling
Choose GitOps if most of these are true:
- ☐ Kubernetes is a core production platform
- ☐ Several services, environments, or clusters
- ☐ You need a reviewable audit trail for production changes
- ☐ Drift / environment inconsistency has already caused you incidents
- ☐ You can invest in Git discipline and in running the operator well
Choose the hybrid (most teams): CI for build/test/scan/publish, GitOps for deployment and reconciliation — and revisit the boundary once a year, not once a week.
14. FAQ
Can I use GitOps and traditional CI/CD together?
Yes — and most GitOps adopters do. CI builds, tests, scans, and publishes the artifact. GitOps deploys that artifact and keeps the environment reconciled with Git. They solve different halves of the problem.
Is GitOps only for Kubernetes?
Primarily in practice. GitOps needs a declarative target that an operator can continuously reconcile, and Kubernetes is the most common such system. You can apply GitOps ideas (declarative desired state in Git, automated reconciliation) elsewhere, but expect the richest tooling in the Kubernetes ecosystem.
Does GitOps replace Jenkins or GitHub Actions?
No. It replaces the deployment part of what those tools are often used for. You still need CI to build and test code and publish images. Many teams keep their existing CI tool and simply remove its production deployment credentials once GitOps is in place.
What happens if Git goes down?
Running workloads keep running — the operator cannot fetch new desired state, so new deployments and drift correction pause until Git is available again. This is why your Git hosting's availability and your repository backups matter more once you adopt GitOps.
How do I handle secrets in GitOps?
Never in plain text in Git. Use a pattern that keeps the secret in a dedicated secrets system and delivers it to the cluster — for example an external-secrets integration with a cloud secret manager / Vault, or an encrypted-in-Git tool such as Sealed Secrets where decryption happens in the cluster only. Pick one pattern, document it, and use it consistently.
Should app code and deployment config live in the same repository?
Both layouts are used. A separate config repository makes a deployment change small and easy to review/protect, and lets app teams and platform teams have different permissions. A single repository keeps everything in one place but needs careful per-path protection so an app change does not silently become an unreviewed production change. Choose based on your team boundaries and review model.
Is GitOps harder to learn?
Initially, usually yes — there is a new operator, a new debugging model, and stricter Git discipline. Teams commonly find day-to-day deployment and rollback simpler once the initial setup and templates are in place. Budget real time for that learning curve; do not promise your team it is "free".
Argo CD or Flux — which should I start with?
Start with the one your team can operate best. Try both on a throwaway cluster with the same sample app: deploy it, break it (change something by hand), roll it back, and upgrade the operator. The tool you can debug at 2 a.m. is the right tool for you.
Does GitOps work for database migrations?
Carefully, and not automatically. An operator can run a migration as a job, but ordering, backward compatibility (expand/contract migrations), and rollback of data are application concerns that GitOps does not solve. Design migrations to be safe with both the old and new app version wherever possible.
15. Conclusion
Traditional CI/CD answers: "Run these steps to deploy."
GitOps answers: "This is what production should look like — continuously make it so, and show me any difference."
If your world is a simple app on a simple target, the first answer is enough, and adding an operator is overhead you do not need. If your world is Kubernetes at any real scale — multiple services, environments, or clusters, with audit and drift problems you can name — the second answer will save you incidents, arguments, and archaeology.
Start where most teams end up anyway: keep your CI, add GitOps for deployment on one service, protect the config repository like production, and expand only once it is boring.
About the author
Rabin Adhikari — DevOps engineer and founder of DevOps Enginer. Specializes in Kubernetes, GitOps, CI/CD automation, and cloud-native architecture. Rabin writes practical guides that help teams build modern, secure deployment pipelines.
Related guides
- how self-healing infrastructure works in Kubernetes — how reconciliation and drift correction connect to automatic recovery in Kubernetes.
- Serverless vs Containers: how to choose the right architecture — for workloads where a traditional pipeline or serverless platform fits better than GitOps.
Top comments (0)