The breaking point was a power cut. The lab came back up and roughly nothing did, because every service on it had been started by hand with a docker run command I'd typed weeks ago and not written down. I had a dozen containers, each with its own remembered-incantation of ports and volumes and environment variables, all of it living in my head and my shell history rather than anywhere I could read. The lab worked right up until it stopped, and then I spent an evening reconstructing it from memory and docker inspect.
So I moved the lot into a single docker-compose.yml, and the homelab has been a far calmer place since.
The pitch for Compose, in a homelab, isn't orchestration or scaling. It's that the entire state of your services becomes one text file you can read, edit, and commit to git. Every container, its image, its ports, its volumes, what it depends on, all declared in one place. docker-compose up -d brings the whole stack up; down takes it away. A reboot is no longer an event.
version: "3.7"
services:
reverse-proxy:
image: traefik:v2.0
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-certs:/certs
networks:
- house
some-service:
image: someorg/some-service:latest
restart: unless-stopped
volumes:
- some-data:/data
networks:
- house
labels:
- "traefik.enable=true"
- "traefik.http.routers.some.rule=Host(`some.house.lan`)"
networks:
house:
driver: bridge
volumes:
traefik-certs:
some-data:
Three things did most of the work for me.
A shared network. Putting everything on one user-defined bridge means containers can reach each other by service name. No more guessing IPs or publishing internal ports to the host just so one container can talk to another. The reverse proxy reaches every backend by its Compose name, full stop.
Named volumes, not bind mounts scattered across the filesystem. Declaring volumes in the file means I can see, in one place, exactly where every service keeps its state, which is the only stuff that actually matters when it comes to backups. Back up the named volumes and you've backed up the lab; the containers themselves are disposable.
A reverse proxy out front (Traefik here, which reads container labels and wires up routing automatically). I get sensible hostnames instead of a wall of port numbers, and adding a new service is a couple of labels rather than another port to remember.
restart: unless-stopped on each service is the line that actually fixed the power-cut problem. After a reboot, Docker brings the stack back exactly as declared, no human required.
It's not Kubernetes and it isn't trying to be. For a single box running the household's odds and ends, Compose hits the sweet spot: declarative enough that a reboot is boring, simple enough that I can read the whole config in one screen and understand it six months later. The real win wasn't any single feature. It was moving the lab out of my head and into a file I can git push.