Handing cluster credentials to a build pipeline is the arrangement most teams start with and the one that makes deployments hard to audit. Inverting it puts a controller inside the cluster and a commit at the centre, which changes both the security story and who is effectively able to change production. This covers the setup, and the two settings that define how it feels to work with.

The pitch for GitOps is that a repository becomes the description of what is running. The part that changes how a team works is narrower: nobody applies anything by hand any more, so the question "what is deployed and who changed it" has an answer instead of an investigation.

Pull, Not Push

A CI pipeline that ends in kubectl apply is push deployment. The pipeline holds cluster credentials, it decides when the cluster changes, and if it fails halfway the cluster is in a state nothing describes.

Argo CD inverts it. A controller inside the cluster watches a repository and continuously reconciles the cluster towards it. No external system holds cluster credentials, the desired state is a commit, and drift is detected rather than accumulating quietly.

The security consequence is the one worth stating to a reviewer: your CI system no longer needs cluster admin. It builds an image and updates a tag in Git, and that is the extent of its power.

Install and First Application

kubectl create namespace argocd
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.example.com/infra/manifests.git
    targetRevision: main
    path: apps/web/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Change that initial admin password and then disable the local admin account in favour of your identity provider. An Argo CD instance with cluster-admin and a shared password is a serious liability, and it is the default state after installation.

The Two Flags That Define the Experience

selfHeal: true reverts manual changes. Somebody scales a deployment with kubectl and the controller puts it back. This is the behaviour people want and also the one that surprises them during an incident, when a deliberate emergency change keeps being undone. Know it is on, and know how to pause an application.

prune: true deletes resources removed from Git. Without it, deleting a manifest leaves the object running forever, and Git stops describing the cluster. With it, a bad merge deletes production. Enable it, and treat the repository's main branch with the review discipline that implies.

Start with both off on a first application, watch what the controller says it would do, then turn them on. The diff view is honest about what it is about to change.

Repository Layout

The structural decision that matters is not tooling, it is whether environments are branches or directories.

ApproachWhy people choose itProblem
Branch per environmentFamiliar from application codeMerging drags unrelated changes; divergence is invisible
Directory per environmentAll environments visible in one commitRequires overlays to avoid duplication
Repository per environmentHard separation of accessPromotion becomes a copy between repos

Directories with Kustomize overlays is the arrangement that causes the least trouble: a base plus per-environment patches, so a change to shared configuration is one edit and a promotion is a change to one image tag in one file. Our guide to Helm charts covers the case where upstream packaging is a chart, which Argo CD renders happily; using Helm as a renderer while the controller does the deploying is the coherent combination.

Secrets Are the Unsolved Part

Everything in Git means secrets in Git, and a Kubernetes Secret is base64, not encryption. Three workable answers.

Sealed Secrets. Encrypt with a public key, commit the ciphertext, and a controller in the cluster decrypts it. Simple, self-contained, and the controller's private key becomes the thing you must back up or lose everything.

External Secrets Operator. Git holds a reference; the operator fetches the value from a real secret manager at runtime. Better separation and it needs a secret manager to exist.

SOPS with age or KMS. Encrypt values in place, decrypt at render time. Good developer experience, and key distribution is yours to solve.

Pick one and use it everywhere. A repository where some secrets are sealed, some are referenced and one is plaintext because it was urgent is the arrangement that leaks. Our guide to encryption keys and recovery covers custody of whichever key ends up load-bearing.

App of Apps, and Projects

Managing Application resources by hand does not scale past a handful. Two mechanisms handle it.

The app-of-apps pattern makes one Application whose source is a directory of Application manifests, so adding a service is a commit rather than a kubectl apply. ApplicationSets generate them from a list or from repository structure, which is the better answer once the count is in the dozens.

AppProjects are the multi-tenancy control and the more important of the two. A project restricts which repositories an application may come from, which namespaces it may deploy to, and which resource kinds it may create. Without them, any team that can commit to any watched repository can create a cluster-scoped role. On a shared cluster, configure projects before onboarding the second team, not after.

What GitOps Does Not Give You

Three honest limits, because the pitch tends to overreach.

It does not remove the need for backups. Git holds your intent; it does not hold persistent volume data, nor state an operator generated. Our guide to cluster backup with Velero covers what remains after Git covers what it can.

It does not make rollback trivial where a database migration ran. Reverting the commit reverts the manifests; it does not revert a schema. Our guide to upgrade strategy covers that asymmetry.

And it does not help if the repository is not reviewed. A controller that faithfully applies whatever lands on main has made merge access equivalent to production access. That is a governance change, and pretending otherwise is how teams get a worse outcome than the pipeline they replaced.

The Cluster to Run It On

Argo CD is a controller with a web interface, so it is modest to run and it becomes the route through which every deployment happens, which puts its availability on the critical path for shipping.

MassiveGRID's managed Kubernetes starts from $0.03474 per hour, roughly $25.37 a month, with resources billed in cloudlets of 128 MiB RAM and 400 MHz CPU, so a controller costs close to what it consumes rather than a whole instance. Underneath, Proxmox high-availability clustering with automatic failover over Ceph storage replicating every block three times across independent NVMe drives keeps etcd and the controller's state available through a node failure. Where the Git repository is also yours to run, our guide to self-hosting GitLab covers that side, and DevOps support covers the pipeline design if nobody internally owns it.

Clusters can be ordered across a partner footprint of more than 700 datacenters in 85 metros, 30 countries and six continents, with auto-provisioning in New York, London, Frankfurt and Singapore.

Further Reading