Home lab: self-hosted, LAN-only infrastructure
A self-hosted home lab on Arch Linux: one compose project per service, a single CLI for stacks, secrets and routes, and nothing exposed to the internet.
Overview
The home lab is a set of self-hosted services on Arch Linux, reachable only from inside the home network: a git forge, a password manager, an inventory app, local DNS and more. It runs on a desktop PC today and is built to move to a dedicated server without being rewritten. Two rules shaped it: nothing is reachable from the internet, and every host-specific value lives in one place.
Design at a glance
| Concern | Implementation | Why |
|---|---|---|
| Layout | One Docker Compose project per service, in git | Each service starts, stops and moves on its own |
| Operations | One Bash CLI, lab, for stacks, secrets, routes and health checks |
One entry point for everything |
| Entry point | Caddy is the only service on the LAN; the rest bind to loopback | One place to enforce TLS and access rules |
| TLS | Wildcard certificate via ACME DNS-01 | Trusted certificates with no inbound internet port |
| DNS | AdGuard Home for local names and ad blocking | HTTPS names on the LAN, less tracking |
| Secrets | sops + age, main key on a hardware security key | Never stored in plaintext on disk |
| Firewall | nftables, inbound default-drop | Only the proxy and DNS reachable, LAN only |
Technical deep dive
One CLI. lab up, down and logs wrap Docker Compose, so every stack starts the same way.
lab route add <name> <port> adds a reverse-proxy site and reloads Caddy; lab secrets handles key
setup, importing, editing and re-encryption; lab status runs a health check after reboots. Compose
files use only relative paths and work with Docker or Podman.
Proxy and TLS. Caddy, built with a DNS provider plugin, gets one wildcard certificate through the DNS-01 challenge using an API token scoped to a single zone. Validation happens over DNS, so no port is opened to the internet, yet phones and tablets get certificates they already trust, with no private CA to install. Behind the firewall, the proxy also refuses clients outside private address ranges.
Secrets. Runtime secrets are encrypted with sops and age and committed to git as ciphertext.
Starting a stack runs sops exec-env, so decrypted values exist only in the compose process, never in
a file. There are three recipients: a hardware security key for daily use (each decryption needs a
touch), an offline passphrase-protected backup key, and later the server’s own key for unattended
restarts.
Agent access to git. The self-hosted Forgejo has a separate, non-admin account for AI agents. A git credential helper returns a narrowly scoped token only inside an agent session and otherwise passes through to the owner’s login. Agents can push only to one organization’s repositories, commits keep the human author and signature, and the activity log shows which pushes came from an agent.
Portability. The LAN address, domain, user IDs and time zone live in one env file that lab loads
before each stack’s own settings. A migration runbook covers the server move: add the server’s key as a
recipient, copy the lab volume with ownership intact, edit that one file, start the stacks, repoint
local DNS.
What stays off the lab. The public website is a static site on Cloudflare Pages. A portfolio has no reason to depend on a LAN-only lab that is about to change hardware, and hosting it elsewhere keeps the home address out of public DNS.
Key challenge: keeping secrets out of plaintext without making daily use painful. The encryption was the easy part; the work was around it: decrypting straight into the process, a preflight that checks the hardware key’s state before the age plugin touches it, a backup key whose private half exists only in memory, and recovery material kept outside the lab, since the password manager that would normally hold it runs inside the lab and depends on these same secrets.