Skip to main content

GitOps with Argo CD for Small Teams: A Practical Guide

·1845 words·9 mins
Author
Maksim P.
DevOps Engineer / SRE

TL;DR

  • GitOps means your Git repo is the source of truth for what runs in your cluster — no more manual kubectl apply in prod
  • Argo CD watches your repo and keeps your cluster in sync automatically
  • For small teams, it cuts down on deployment errors and makes rollbacks almost boring (in the good way)
  • Setup takes a few hours, not a few weeks — you do not need a platform team
  • Skip the advanced features (SSO, multi-cluster, app-of-apps) until you actually need them

This guide is for small engineering teams — say three to ten people — running Kubernetes and tired of “who deployed what and when.” It assumes you have a working cluster and some Kubernetes manifests or Helm charts already. If you are still deciding whether Kubernetes is right for you at this stage, that is a different conversation.


What GitOps actually means
#

GitOps is not a product. It is a practice: you store your desired infrastructure and application state in Git, and you use tooling to reconcile what is running in your cluster with what is declared in that repo.

The practical result: you stop running kubectl apply -f from your laptop or from a CI job with a hardcoded kubeconfig. Instead, you commit a change to Git and something else — Argo CD in this case — applies it to the cluster on your behalf.

A few properties fall out of this automatically. Your deployment history is your Git history. Rollback is git revert. You always know what is supposed to be running because it is written down. Drift between what is declared and what is actually running gets caught, because the tool is watching.

None of this is magic. It is just discipline enforced by software rather than by hopes and post-mortems.


When GitOps is worth adopting for small teams
#

GitOps earns its complexity cost when you have at least one of the following problems:

Deployments are a source of anxiety. If pushing to production feels like gambling, having an auditable, repeatable process is worth the setup time.

More than one person touches production. As soon as two engineers can both run kubectl apply, you have a coordination problem. GitOps makes the repo the single point of change.

You want rollbacks that actually work. Reverting a Git commit and watching the cluster catch up is substantially less stressful than trying to remember which image tag was running before.

You are already on Kubernetes. If you are running plain VMs or a PaaS, GitOps tooling is probably not the right fit yet. It is built for clusters.

If none of these apply, keep it simple. A shell script and a CI job might genuinely be enough. No shame in that.


Argo CD vs Flux: quick comparison
#

Both tools do the same fundamental job: sync a Git repo to a Kubernetes cluster. The differences are mostly in interface and philosophy.

Argo CD ships with a web UI that is genuinely useful. You can see the sync status of all your apps, trigger syncs manually, inspect diffs, and trace errors without touching the CLI. For teams that are new to GitOps or that want visibility without learning another command-line tool, this matters.

Flux is more CLI-native and Kubernetes-native — it is built entirely on custom resources and integrates tightly with tools like Helm Controller and Kustomize Controller as separate components. It is a reasonable choice if you want minimal UI overhead and prefer to live in kubectl.

For most small teams adopting GitOps for the first time, Argo CD wins on accessibility. The UI reduces the learning curve. You can see what is happening without reading logs.

Neither is wrong. Flux is not harder, just different. If your team is already comfortable in the terminal and wants fewer moving parts, evaluate it. For everyone else, start with Argo CD.


Setting up Argo CD: the minimal path
#

Install Argo CD into your cluster using the official manifests:

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

Wait for pods to come up, then access the UI by port-forwarding (for now):

kubectl port-forward svc/argocd-server -n argocd 8080:443

Retrieve the initial admin password:

kubectl get secret argocd-initial-admin-secret -n argocd \
  -o jsonpath="{.data.password}" | base64 -d

Log in at https://localhost:8080 with username admin and the password above. Change it immediately.

Now register your application. The core primitive in Argo CD is the Application resource — a Kubernetes object that tells Argo CD where your manifests live and where to deploy them:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/your-repo.git
    targetRevision: main
    path: kubernetes/my-app
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  syncPolicy:
    automated:
      prune: true      # see the warning below before you turn this on
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

A word on those two flags, because the example above is the aggressive configuration and most tutorials hand it to you without comment. prune: true means Argo CD deletes cluster objects that are no longer in Git — including objects that vanished from its view because you mistyped path: or renamed a directory. selfHeal: true means it reverts anything changed outside Git, which is correct on a normal Tuesday and infuriating at 02:00 when you’re scaling a Deployment by hand and Argo CD keeps putting it back. Neither is wrong, but for your first app point it at a namespace you don’t mind losing, watch a sync or two, and turn them on once you trust the path. If you want the safety on from the start, run with prune: false for a week and read the “out of sync” diffs instead of letting them execute.

While you’re here, add a finalizer to the Application itself:

metadata:
  name: my-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io

This makes deletion behave the way you’d expect: kubectl delete application my-app cleans up the resources it deployed, rather than orphaning them in the cluster with nothing left tracking them.

Apply it:

kubectl apply -f my-app.yaml

Argo CD will immediately pull the repo, compare what is declared against what is running, and sync. If you enabled automated sync (as shown above), it will keep doing this whenever the repo changes.

That is the core of it. The rest is tuning.


Day-2 operations: syncing, rollbacks, alerts
#

Syncing. With automated sync enabled, Argo CD polls your repo roughly every three minutes by default. You can reduce latency by configuring a webhook from GitHub or GitLab to trigger a sync on push. It is a five-minute setup and worth doing.

Rollbacks. Find the Git commit you want to revert to, revert it (or just roll the image tag back), push, and watch the cluster reconcile. You can also trigger a sync to a specific revision from the Argo CD UI if you need to move fast. The prune: true setting ensures resources removed from Git get removed from the cluster, so you do not end up with orphaned objects.

git revert rolls back your manifests, not your database. This is the limit of GitOps and it catches people out precisely because everything else about the rollback is so clean.

If the release that is going out also changed the schema, reverting the manifests puts the old application back in front of the new schema — and the old code does not know about the column you renamed. Argo CD will report Synced and Healthy while the application throws errors, because from the cluster’s point of view nothing is wrong.

The way out is not a GitOps feature. It is making every schema change backward compatible for one release, so the previous version of the application can always run against the current schema. That is the expand-contract pattern, and it takes three deploys rather than two. Until that discipline is in place, treat any release containing a migration as one you cannot roll back by reverting a commit.

Alerts. Argo CD exposes Prometheus metrics out of the box. If you have a metrics stack, scrape them and alert on argocd_app_info{sync_status="OutOfSync"} persisting longer than a few minutes. If you do not have a metrics stack yet, the UI is good enough early on — check it after deployments.

Health checks. Argo CD tracks the health of your Kubernetes resources automatically. A deployment that is stuck in rollout will show as “Degraded” in the UI, not silently passing. This alone catches a surprising number of deploy failures.


What to skip early on
#

SSO and RBAC. The built-in admin account is fine while the UI is only reachable through kubectl port-forward — the cluster’s own RBAC is the real gate at that point, and everyone using it is already a cluster admin.

Two conditions end that, though, and the first one arrives sooner than you’d think. The moment you put Argo CD behind an ingress, a shared password is your entire authentication story, and anyone who has ever seen it keeps access after they leave. The second is subtler and cuts against the reason you adopted GitOps: this guide opened with “who deployed what and when,” and a shared admin account erases exactly that. Argo CD logs every manual sync, rollback, and app change with the identity that performed it — if that identity is always admin, the audit trail records that admin did everything, which is the same as recording nothing. Git history covers commits; it does not cover who hit Sync in the UI at 3 AM.

So: shared admin until the first ingress. After that, either wire up SSO (dex.config for GitHub/Google, or OIDC straight to your IdP) or, if that feels heavy for four people, add local accounts in argocd-cm — one per engineer, each with their own password and API key. Local accounts take ten minutes and get you named identities in the audit log, which is 90% of the benefit. Either way, disable the built-in admin (admin.enabled: "false") once real accounts exist.

App-of-apps pattern. This is a way to manage many Argo CD Applications with a parent Application. It is useful at scale. At small scale, it is a layer of indirection you do not need.

Multi-cluster management. Argo CD can manage multiple clusters from a single control plane. If you have one cluster, ignore this feature entirely.

Helm chart overrides via Argo CD values. If you are using Helm, keep your values files in Git alongside the rest of your configuration. Avoid building complex value override chains through Argo CD’s API — it becomes hard to follow quickly.

The CLI (argocd binary) as your primary interface. The UI is good. Use it. The CLI is useful for scripting and troubleshooting but is not the right primary interface for day-to-day work on a small team.

The pattern here is: start minimal, add complexity when you have a concrete problem it solves. Argo CD is not a platform you build out in advance — it is a tool you grow into.


Related reads #

Reply by Email