Ramblings of an aging IT geek
← Ramblings of an aging IT geek
homelab

one compose file to rule the homelab

How I moved every service in the house under a single Docker Compose project and stopped pretending each one needed its own snowflake setup.

A server rack with cabling

For years the homelab was a pile of half-remembered docker run commands. Pihole here, Jellyfin there, a Grafana that I'd started "just to try it" and never turned off. Every one of them had its own ports, its own restart policy if I'd remembered to set one, and its own way of dying quietly when the box rebooted. Bringing the lot back up after a power cut was an afternoon of archaeology.

So I did the boring thing and put everything in one Compose project. Not one giant docker-compose.yml, exactly, but one directory, one .env, and a stack I can bring up with a single command.

the layout

The structure is deliberately dull:

homelab/
  .env
  docker-compose.yml
  pihole/
  jellyfin/
  grafana/
  prometheus/

Each service gets a folder for its persistent data, bind-mounted rather than living in a named volume, because when something goes wrong at 23:00 I want to ls the config, not go spelunking in /var/lib/docker/volumes. The .env holds the handful of things that actually change between my setup and anyone else's: the timezone, the data root, the LAN subnet.

services:
  pihole:
    image: pihole/pihole:latest
    env_file: .env
    volumes:
      - ./pihole/etc:/etc/pihole
    restart: unless-stopped

restart: unless-stopped on every service. That one line is most of why the house now survives a reboot without me. The other half is that Compose brings things up in a predictable order and I no longer have to remember which container needs the database running first, because depends_on does it.

A homelab shelf of small servers and a switch

networks, not port soup

The thing that actually made this pleasant was stopping the port-mapping habit. Instead of exposing every service on a different high port and memorising which was which, internal services talk to each other over a Compose network by name. Only the things that genuinely face me, the reverse proxy and a couple of UIs, publish a port.

So Grafana reaches Prometheus at http://prometheus:9090, not some IP I'd have to keep current. When I move the whole lot to a different machine, nothing in the configs changes, because none of it was ever pinned to a host address.

what it bought me

The real win isn't elegance, it's that the state of the house is now a git repo. The Compose file and the configs are version-controlled, the data directories are excluded and backed up separately, and a fresh machine is git clone then docker compose up -d. I rebuilt the host onto a new SSD last weekend and the whole stack was back in about ten minutes, most of which was waiting for images to pull.

It is not clever. There's no orchestrator, no Kubernetes, no fleet management for four containers and a Raspberry Pi. But it's legible, and when something breaks I know exactly where to look. For a house, that's the whole point.