What problem GitOps actually solves
The problem GitOps solves is configuration drift. Without it, the infrastructure becomes whatever the last person to log into a server decided to type. Six months in, no one knows why a particular config file has the value it has, who changed it, or whether the change is reproducible if the server has to be rebuilt. The institutional knowledge lives in the operator's head and dies when the operator leaves.
GitOps closes this gap by making the repository the canonical state. The infrastructure is reconciled to match. If the live state diverges from the repository, the discrepancy is an alert. If a change is needed, the change is a commit. If the server has to be rebuilt, you check out the repository and apply it.
For a small team, the immediate operational benefit is that anyone who can read the repository can understand the infrastructure. The institutional knowledge stops being implicit. This is the difference between a stack that is the architect's secret and a stack that the next person can take over.
The minimum viable pattern
Our GitOps pattern, stripped to the minimum:
- One repository contains every configuration file for every service we run. Database configs, reverse-proxy configs, container compositions, dashboard JSON, alert rules, workflow exports, ACLs.
- Each service has a directory. Each directory has a README explaining what the service does and how to deploy it.
- A small script in the repository root applies the configuration to the live machines. The script is idempotent — running it twice in a row produces the same result.
- Every change to production is a commit on the main branch. There is a one-line CI step that runs the apply script after a successful merge.
- Secrets are stored separately, in a credential store, and referenced from the configuration files by name. The repository can be public or shared without exposing credentials.
That is it. No clusters, no controllers, no custom resources. The discipline is in the consistency of the pattern, not the sophistication of the tooling.
Reproducible deployments without containers everywhere
One of the under-discussed aspects of GitOps for small operators is that you do not need to containerise everything. The discipline is about reproducibility from the repository, not about a specific runtime.
Our split: services with a non-trivial dependency tree run in containers, defined by container compositions checked into the repository. Services that are simple processes — say, a small Go binary or a Python script — run as system services, with their service-manager configuration files checked in.
The reproducibility test is the same in both cases: can a fresh machine, with nothing installed, become a running copy of this service by checking out the repository and running the apply script? If yes, you have GitOps. If no, you have a checked-in pile of configuration files that look like GitOps but cannot be relied upon to rebuild the system.
Drift detection without a controller
Industrial GitOps usually involves a controller process running inside the cluster, continuously reconciling live state to repository state. For a small team, the same job can be done with a nightly job that compares the live configuration against the repository and alerts if there is a divergence.
The check is mechanical. For each service, hash the relevant configuration files on the live machine, hash the same files in the repository, compare. If they match, log success. If they diverge, send an alert with the diff. The alert is loud because, in a properly-disciplined stack, the only legitimate way for a config file to change is via a commit. Manual edits on the server are an anti-pattern, and the drift detector exists to catch them.
This is a fifty-line script. It runs in a few seconds. The benefit is enormous: you can never have a long-term divergence between what the repository says and what the infrastructure does, because the divergence will be flagged within twenty-four hours.
What it takes to keep the discipline
The pattern is easy. The discipline is the hard part. Three habits make it work:
No SSH-and-edit. Once a configuration file is in the repository, it is edited in the repository, committed, and reapplied. SSH-and-edit is a temptation when something is broken at three in the morning. The discipline is to fix it on the running system, copy the fix back into the repository, commit, and let the next apply pick it up. Without this discipline the repository is not the source of truth.
Commit messages explain why, not what. The diff shows what changed. The commit message records why. Six months later, the why is the only thing that helps you understand whether a configuration is still correct.
Apply often. The apply script should run on every merge to main, automatically. If applies are rare and manual, divergence accumulates and the pain of catching up grows. Frequent small applies are the cheapest reliability mechanism in the pattern.
The takeaway
The Kubernetes-shaped version of GitOps is not wrong; it is just sized for organisations that are not most readers. The small-team version is a repository, an apply script, a drift detector, and the discipline to never edit a server directly. The operational dividend is the ability to rebuild any machine from scratch in minutes, to onboard a new operator without a multi-week handover, and to know — at any moment — exactly what the infrastructure is configured to do.
If you are running a stack that does not have GitOps discipline today, the migration is incremental. Pick one service, write its directory, commit its config, run the apply script. Repeat for the next service. Within a quarter the whole stack is in the repository, and the institutional knowledge has stopped being implicit. That is the moment the architecture stops being the architect's secret and starts being a real asset.
Working on this?
For operators evaluating sovereign-infrastructure architecture for a business of meaningful scale, we run a quarterly cohort of stack-design engagements.
Get in touch