Web AI · 5 min read

The whole infrastructure in one GitHub repo, with an AI

Three servers, a dozen apps, secrets and production deploys in one git repo, written mostly by Claude Code. What is inside, who does what, and what it costs me.

About a month ago I moved all my sites from Google Cloud to Hetzner. The bill went from about €100 to €34 a month, but the interesting part is not the price. It is how it happened and how I have run things since: the whole server life lives in one GitHub repo, simply called infra, and most of it was written by an AI (Claude Code), with me saying what I want, checking, and giving the OK.

This is the first post of a series. Here is the big picture; the secrets (SOPS) and the infrastructure itself get posts of their own.

What is inside

Three servers in Germany (a frontend with Docker and Traefik, Postgres, and one for the Docker registry, Redis and the monitoring) and a dozen applications: shops, this blog, an OAuth portal, a few services. Everything that touches them is in the repo:

  • each application’s compose file and Traefik’s configuration;
  • the Postgres configuration, the backups and their timers;
  • the registry, Redis, GlitchTip (errors and uptime), Grafana with logs and metrics;
  • the scripts: creating servers, the firewall rules, changing an IP, deploying, pruning old images, a weekly vulnerability scan of the images;
  • every server’s secrets, encrypted;
  • AGENTS.md: the notes the AI reads at the start of every session and adds to at its end, with dates;
  • TODO.md: what comes next.

If a server disappears tomorrow, it is rebuilt from the repo and the backups by following the notes, not from memory.

The AI’s part

I have tried keeping documentation before. It lasted two weeks. The difference now is that the documentation is written by whoever does the work: after every change the AI writes into AGENTS.md what was done, when and why, and the next session starts from there. The file is around 1500 lines and is the most useful thing in the repo: “why port 465 doesn’t work” (Hetzner blocks outgoing 25 and 465, we send through 587), “how to roll an app back”, “which key is where”.

The rules are simple and live in the same file:

  • nothing in production without an explicit OK from me: servers, firewall, DNS, deploys;
  • no secrets in git, except encrypted ones;
  • every change gets written down.

I read what it does and why. That is the part that cannot be delegated: an AI writes confidently when it is wrong too. But the difference between “write a backup rotation script” and “check the script that is already written and tested” is large, and that is exactly what is left for me.

The secrets: SOPS + age

Every server’s .env files live in the repo, encrypted with SOPS and age. The variable names are readable, the values are not. Only I hold the keys that decrypt them, in a safe place; the servers never get a key. One command decrypts a file on the computer and sends it over SSH to its server. A git hook and CI refuse an unencrypted .env.

The result: a history of every secret, a backup by default, and zero chance of committing a password by mistake. It deserves its own post; it will get one.

Deploys: a release queue

A deploy used to be “build on the laptop, push, ssh, docker compose up”. Now it is one command:

rel blog 0.2.1

It makes a signed git tag and pushes it. On the registry server a small Go service watches the tags, checks the signature (only my keys pass), runs the tests, does the docker build, scans the image for vulnerabilities, pushes it to the registry, deploys over SSH, checks the site and watches GlitchTip for two minutes for new errors. If something breaks, it rolls back to the previous version by itself. Some apps have a window (one shop deploys only after 22:00); the job waits.

No GitHub Actions: releases don’t depend on someone else’s CI, and the signed tag is my OK. I asked about the ready-made tools too: Komodo, Dockhand and Ansible. The answer that convinced me: Komodo and Dockhand would replace scripts that already work, and bring their own agent with access to the Docker socket on every server plus a web UI to protect and update. Ansible fills a real gap (rebuilding a server from scratch with one command), but with three servers a bootstrap script per server does the same. Everything I wanted is about a thousand lines of Go, and I know what every line does.

What I get

One place for everything. History: git log says when and why a port, a key or a version changed. Peace of mind: backups on three levels, email alerts for a full disk, a missed backup, an expiring certificate, a site that is down. And time: things I had postponed for years (monitoring, image scans, secret rotation) took an evening.

What it costs me: I have to read. Every day. And I have to keep the notes clean, because the AI starts from them, and a mistake there repeats itself.

Next post: SOPS, with examples.