In 2017, a small team at Weaveworks coined a word that would reshape how platform teams think about deployment: GitOps. The idea was almost provocatively simple. Instead of running `kubectl apply` from a laptop or wiring a CI pipeline to push changes into a cluster, you declare what you want in Git and let an automated agent inside the cluster pull it down. Nearly a decade later, that pull-based model is the default mental model for Kubernetes delivery, and the two flagship controllers, Argo CD and Flux, are both CNCF-graduated projects.
This tutorial walks through the whole loop: what GitOps actually means, how to set up a repo, install a controller, watch a sync happen, and handle the traps that trip up almost every first attempt. It stays tool-neutral where it can, uses Argo CD and Flux as the two reference implementations, and flags the version-sensitive bits you should re-check against live docs before you copy any command.
What GitOps Really Means (and What It Does Not)
Strip away the marketing and GitOps rests on four principles published by the OpenGitOps working group: the system is declarative, the desired state is versioned and immutable, software agents pull that state automatically, and controllers continuously reconcile reality against it.
That fourth principle is the one people underestimate. A push-based CI pipeline applies a change once and walks away. If someone later edits a Deployment by hand, the pipeline has no idea. A GitOps controller, by contrast, compares desired versus actual state on a loop. Argo CD surfaces this as an OutOfSync status; Flux handles it through configurable reconciliation intervals. Hand-edit a replica count in a GitOps-managed cluster and the controller will quietly revert it. That is drift correction working as designed, and it is also the first thing that surprises newcomers.
A quick contrast helps:
| Dimension | Push-based CI/CD | Pull-based GitOps |
|---|---|---|
| Who applies changes | Pipeline runner with cluster credentials | Agent running inside the cluster |
| Credential exposure | Cluster admin creds live in CI | Agent pulls from Git; no inbound access needed |
| Drift detection | None by default | Continuous comparison and auto-heal |
| Audit trail | Pipeline logs | Git history plus controller events |
| Rollback | Re-run an older pipeline | Revert a commit |
The security difference is not cosmetic. Push pipelines usually need a long-lived kubeconfig sitting in a CI secret store. A pull agent needs read access to a repo and nothing else, which shrinks the blast radius considerably.
Prerequisites Before Your First Sync

You need four things in place. First, a Kubernetes cluster you can administer, whether that is kind, minikube, or a managed offering. Second, `kubectl` configured against it. Third, a Git repository you control. Fourth, a GitOps controller installed into the cluster.
One practical warning before you start: install commands and CRD versions change often. A tutorial written more than a year ago may reference flags that no longer exist or Helm chart values that have been renamed. Always cross-check the install step against the project’s current getting-started page rather than trusting a blog post, including this one. The conceptual flow below is stable; the exact syntax is not.
Step 1: Structure the Repository
The repo is the heart of the whole exercise, so give it a shape you can live with. A common layout separates applications from cluster-wide configuration:
- `apps/` holds one directory per application, each with a `kustomization.yaml` or a Helm chart reference
- `clusters/` holds per-environment entry points (staging, production) that point at the app directories
- `infrastructure/` holds shared pieces like ingress controllers, cert-manager, or monitoring stacks
Two design choices matter more than the folder names. First, decide between Kustomize overlays and Helm values early, because mixing them ad hoc creates confusion later. Second, keep environment differences in small overlay files rather than duplicating whole manifests. A staging overlay that changes only replica counts and image tags is easy to review; two near-identical copies of every manifest are not.
A minimal starting point is a single Deployment plus a Service for a stateless app. Resist the urge to port your entire production estate on day one. The goal of a first GitOps repo is a successful sync, not completeness. (DevOps)
Step 2: Install a Controller
Both major controllers install cleanly, and both ship official getting-started guides. Pick one for the tutorial; you can evaluate the other afterward.
Argo CD typically installs into its own namespace and exposes a web UI and CLI. After installation you register the Git repo and create an `Application` resource that names the repo path and the target cluster and namespace. The UI then shows a tree of resources with sync status and health per object, which is genuinely useful for debugging.
Flux takes a more composable route. You bootstrap it against a repo, and it commits its own manifests back into that repo. From there you define `GitRepository` and `Kustomization` resources that tell Flux what to fetch and where to apply it. There is a CLI-driven bootstrap flow and a set of controllers you can enable selectively.
The table below summarizes the practical differences beginners notice:
| Aspect | Argo CD | Flux |
|---|---|---|
| Primary interface | Web UI plus CLI | CLI-first, no bundled UI |
| Bootstrap style | Install, then define Applications | Bootstrap writes manifests into your repo |
| Multi-cluster | Built-in cluster registry | Supported via kubeconfig secrets |
| Learning curve | Gentle with the UI | Steeper, more composable |
| Drift visibility | Explicit OutOfSync status | Reconciliation events and logs |
Neither is objectively better. Teams that want visibility for less experienced operators tend to reach for Argo CD’s UI. Teams that prefer everything expressed as manifests and reconciled by controllers tend to prefer Flux.
Step 3: Point the Controller at Git and Watch the Sync
Once the controller knows about your repo, the magic is anticlimactic in the best way. Create the Application (Argo CD) or the GitRepository and Kustomization pair (Flux), then watch the controller fetch, render, and apply. In Argo CD the app moves from Missing to OutOfSync to Synced. In Flux you watch the reconciliation logs until the resources report ready.
Now run the real test. Change the replica count in Git, commit, and push. Within the reconciliation interval, the cluster updates on its own. No pipeline, no credentials on your laptop, no manual apply. That single loop is the entire value proposition, and seeing it once makes every subsequent concept click.
Then test drift. Scale the Deployment manually with `kubectl scale` and wait. The controller should notice the divergence and restore the declared state. If it does not, check whether auto-sync or pruning is enabled; many controllers default to reporting drift rather than correcting it until you opt in.
Step 4: Handle Secrets Before You Regress
This is where most tutorials quietly fail their readers. GitOps stores desired state in Git, and you must never commit plaintext credentials there. Three patterns dominate:
- Sealed Secrets encrypts a secret so only the cluster’s controller can decrypt it; the encrypted form is safe to commit
- SOPS encrypts values with keys managed by a cloud KMS or age, keeping the file structure readable
- External Secrets Operator keeps secrets out of Git entirely and syncs them from a vault at runtime
Pick one before you have secrets to manage, not after. Retrofitting a secrets strategy onto a repo full of plaintext YAML is painful and, frankly, embarrassing if it ever reaches a public remote.
Common Mistakes That Slow Down First Attempts
A few failure modes show up again and again. Installing a controller version that no longer matches the CRD API in your manifests produces cryptic apply errors. Pointing the controller at the wrong repo path yields an app that syncs successfully but deploys nothing. Forgetting to enable pruning leaves orphaned resources behind after you delete a manifest. And enabling auto-sync on production before you trust the repo means a bad commit goes live without a review gate.
The fix for most of these is boring: protect the main branch, require pull request review, and start with manual sync in production until you have watched a dozen reconciliations behave correctly. GitOps automates delivery, not judgment.
How This Fits a Real Delivery Pipeline
GitOps does not replace CI. It complements it. Your CI pipeline still builds images, runs tests, and updates the image tag in the GitOps repo. The GitOps controller then takes over from there. That handoff point, the commit that bumps an image tag, becomes your deployment record. You can trace any running pod back to a commit, a pull request, and a reviewer, which is a far better audit story than scrolling through pipeline logs. (What is GitOps?)
For teams standardizing their infrastructure and delivery workflows, resources like the platform engineering guides at occcy.com can help frame where GitOps sits relative to broader platform work.
Conclusion
GitOps is not complex in basic operation: declare state in Git, let an agent pull it, reconcile continuously. The complexity sits in the details: repo structure, secrets, sync policy, and the discipline of never hand-editing a managed cluster. Start with one small app, get a sync and a drift correction working, then expand. The tools will keep changing versions and flags, but the loop stays the same, and once your team trusts it, you will wonder how you ever shipped with push pipelines.
Frequently Asked Questions
Do I need Kubernetes to use GitOps?
No, but it is by far the most common context. The four principles apply to any system with a declarative interface and a reconciliation agent. In practice, almost every GitOps tutorial and tool assumes Kubernetes, because the controller model maps so cleanly onto cluster resources.
Which is better for beginners, Argo CD or Flux?
Argo CD usually feels friendlier at the start because its web UI shows sync status and resource health visually. Flux rewards teams that prefer a pure manifest and CLI workflow. Both are CNCF-graduated and production-ready, so the choice comes down to team preference rather than capability.
What happens if someone changes the cluster manually?
The controller detects the divergence on its next reconciliation and, depending on configuration, either reports it as out of sync or automatically reverts it. This drift correction is a feature, not a bug, but it does mean ad hoc debugging changes will disappear. Use a break-glass process for emergencies.
How do I keep secrets out of Git?
Use an encryption or externalization pattern. Sealed Secrets and SOPS let you commit encrypted values safely, while External Secrets Operator pulls credentials from a managed vault at runtime. Choose one approach early and apply it consistently across every repository.
Does GitOps work for multiple clusters and environments?
Yes. Argo CD maintains a cluster registry and can target many clusters from one control plane. Flux supports multi-cluster setups through kubeconfig secrets and per-cluster Kustomizations. Most teams use directory or overlay separation to keep staging and production configuration distinct.
How often should the controller reconcile?
Most teams leave the default interval, which is typically a few minutes, and rely on webhooks or immediate reconciliation triggers for faster feedback after a commit. Very short intervals increase API server load without much benefit. Tune it only when you have a measured reason to.
One last practical note: treat the GitOps repo as production code. Add a CODEOWNERS file, a pull request template that asks for the target environment, and a CI check that renders the manifests before merge. These small guardrails cost an afternoon and prevent most of the mistakes described above. When you are ready to grow, add a second environment to prove the overlay pattern, then a second cluster to prove the registry pattern. Each step is incremental, reversible, and reviewable—exactly the properties that make GitOps worth adopting in the first place.


