Skip to content

How this was built

This project was built with Claude Code, an AI coding agent, working as a pair programmer under human direction and review. The git history shows it: every commit carries a Co-Authored-By: Claude trailer.

Division of work

The owner set the goals and the constraints, and made the decisions. Examples:

  • "Maximize automation, minimize interaction."
  • Secrets only in Ansible Vault.
  • Stacks that are "modular and somewhat independently useful for others."
  • Turning down router-wide DNS, because Wi-Fi shouldn't die with the server.
  • Choosing Authelia, Tailscale and this docs site from the options laid out.

The owner reviewed and merged every pull request, and did every step that needed their accounts or sudo.

The agent investigated, proposed plans, implemented, tested and wrote up each change. It also challenged choices: for example, it explained why Homepage belongs in the management stack and not the networking stack. When the owner pushed back, the explanation was revised rather than defended.

Workflow

  1. Investigate before changing. Read the code, logs and live state first. The certificate outage was traced to a desktop VPN app blocking DNS, not to Traefik (ADR 0004).
  2. Plan, then get approval. Larger work was written up as a plan with the stack placement and trade-offs, approved, then split into one pull request at a time.
  3. Test against the real system without risking it. Changes were tried in throwaway containers on the live Docker network, behind the real Traefik and Authelia, and removed afterwards. Some examples:
    • An end-to-end login check: redirect, wrong password, password without two-factor.
    • A throwaway Sonarr to confirm what "external" authentication does.
    • Homepage's host check, and its status logic for each service.
  4. Lint and scan locally, then in CI. yamllint, ansible-lint, shellcheck, docker compose config for every profile combination, and gitleaks.
  5. Write the pull request for a reviewer. Each one covers what changed, why, a security review, what was tested, what wasn't tested live, and the deploy steps in order.
  6. Verify after deploy. For example, the forwarded VPN port was checked against qBittorrent's listening port, and the Tailscale route against the admin console.

Guardrails

  • Secrets never pass through the agent into git. Credentials are generated by scripts straight into the vault, templates reference them, rendered files are gitignored, and credential-handling Ansible tasks use no_log. When a value was accidentally printed to the local terminal, that was reported.
  • Permission checks on risky live actions. The agent's environment blocks actions like weakening a live service's authentication or deleting a volume. The agent then switched to a read-only check, or handed the step to the owner to run deliberately.
  • Project memory as code. CLAUDE.md records the conventions: stack rules, port binding, label patterns and secret handling. Every session starts from the same rules.

Lessons

  • Test before you hand over steps. Early on, deploy instructions were handed over without being tried live first. Since then, every change is exercised against the running system before the PR is opened, and anything that couldn't be is listed as untested.
  • Correct overstated arguments. A claim that putting Homepage in the networking stack would restart Traefik was wrong. It was corrected when questioned, and the real reason (where the API keys end up) was given instead.
  • Small PRs, merged one at a time. Each one could be understood, reviewed and rolled back on its own: port sync, certificates, DNS, stack layout, single sign-on, start page, remote access, docs.

Timeline

PR Change
#2 Migrate Jellyseerr to Seerr; fix the qBittorrent 5.2 login; CI fixes for gitleaks
#3 Sync qBittorrent's listening port with the VPN's forwarded port
#4 Restore HTTPS with one wildcard certificate; encrypted DNS upstreams
#5 Request the wildcard from one router; public DNS-only wildcard record
#6 Stack layout: cloudflared to networking; HSTS and security headers
#7 Authelia single sign-on in a new auth stack; loopback-only admin ports
#8 Homepage start page
#9 Tailscale remote access
#10 This docs site
#12 Family requests inside the Moonfin app; Seerr off the internet