Skip to content
Blog

A pragmatic GitOps baseline for small platform teams

What I set up (and agree on with the team) before anyone installs Argo CD. Spoiler: most of it is conventions, not tools.

Daniel Ulisses2 min read

I’ve helped a fair number of teams move to GitOps by now, and I’ve noticed something a bit uncomfortable: when it goes wrong, it’s almost never Argo CD’s fault.

It goes wrong because nobody sat down and agreed on two boring things: where stuff lives and who’s allowed to change it. So before I install anything, I try to get the team to agree on a baseline that fits on a single page. Here’s roughly what goes on that page.

1. Split repositories by how often they change

This is the layout I keep coming back to for teams of about 3 to 15 engineers:

Repository What it owns How often it changes
infra-live Terraform for accounts, networks, clusters Weekly-ish
platform-addons Cluster add-ons: ingress, cert-manager, Kyverno Weekly-ish
apps-deploy Helm values / Kustomize overlays per environment Many times a day
app repos Source code, Dockerfile, chart Many times a day

People usually push back on pulling deployment config out of the app repo. “Isn’t that just one more repo to deal with?” Fair question. What sells it is this: promoting from staging to production turns into a small pull request that someone actually reads. After a few weeks nobody wants to go back.

2. One “app of apps” per cluster

I like an ApplicationSet that just looks at folders:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-addons
spec:
  generators:
    - git:
        repoURL: https://github.com/acme/platform-addons
        revision: main
        directories:
          - path: addons/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: platform
      source:
        repoURL: https://github.com/acme/platform-addons
        targetRevision: main
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'
      syncPolicy:
        automated: { prune: true, selfHeal: true }

Want a new add-on? Add a folder. Want it gone? Delete the folder. Nobody has to click around in the UI, and nobody has to remember what they clicked.

3. Guardrails first, speed second

A few rules I push for from day one:

  • Policy as code with Kyverno. Start in Audit mode so you don’t break anything, read the reports for a couple of weeks, then flip to Enforce.
  • Terraform plans on every PR, and applies only from CI using short-lived credentials (OIDC). No static keys sitting in a secrets store forever.
  • Treat drift as a bug. At some point selfHeal will undo somebody’s kubectl edit and they’ll be annoyed. Don’t turn it off. That’s your chance to show them the workflow.

4. Teach the workflow, not just the tools

The part I enjoy most is the last one. I run a hands-on session where every engineer ships one change end to end: open the PR, read the plan, merge, watch Argo CD sync, then roll it back.

It sounds basic, but something clicks once people have done it themselves. The platform stops feeling like a black box that only “the DevOps person” understands, and they start using it for real.

If you’re planning a GitOps move, or you do it completely differently and think I’m wrong, I’d love to hear about it.

Written by Daniel Ulisses

Cloud Architect & DevOps Engineer. I write about cloud platforms, GitOps and bringing AI into DevOps workflows. Questions or feedback on this post? I'd love to hear it.

Keep reading