Docker Compose Stacks for Home Labs: Copy-Paste Setups That Actually Work
This page may contain affiliate links.
So you have a mini PC, a spare Raspberry Pi, or an old desktop humming in the corner, and you have decided it is time to run some proper services on it. Congratulations β you are now a home labber, and you are about to discover that the difference between a working home lab and a pile of half-configured containers is usually one thing: Docker Compose.
Compose lets you describe an entire stack in a single YAML file. One command brings it up, one command takes it down, and the file itself becomes your documentation. Below are the setups I actually run and recommend, with the bits people usually get wrong called out. Everything here is copy-paste friendly, but read the comments β the comments are where the value is.
Before you paste anything: the three rules
First, create a folder per stack. Something like ~/stacks/immich, ~/stacks/monitoring. Each folder holds a docker-compose.yml and a .env file. Second, never put secrets directly in the Compose file β use a .env file and reference variables with ${VAR}. Third, pin your image tags. image: postgres:16 is fine. image: postgres:latest is a time bomb that will one day pull a major version and refuse to start against your existing data directory.
Run docker compose up -d to start a stack and docker compose logs -f to watch it boot. If something is misbehaving, docker compose config will show you the fully resolved file with your variables substituted β invaluable for spotting a typo in a port mapping.
Stack 1: The reverse proxy (start here, always)
Everything else depends on this. Caddy is the easiest option because it handles HTTPS certificates automatically, and if you own a domain and point a wildcard DNS record at your server, you get real certificates for internal services.
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Your Caddyfile then stays tiny:
photos.example.co.uk {
reverse_proxy immich-server:2283
}
Note that Caddy needs to be on the same Docker network as the services it proxies. Create a shared external network once with docker network create proxy, then add networks: [proxy] to each stack and declare it as external at the bottom of each file. This one habit saves hours of confusion.
Stack 2: Photos and files (Immich plus a database)
Immich has replaced Google Photos for a lot of home labbers, and for good reason. It is heavier than most stacks because it wants PostgreSQL and Redis alongside it, but Compose handles that neatly. The critical detail is the upload volume β put it on your big disk, not your boot SSD, and back it up separately from the database.
services:
immich-server:
image: ghcr.io/immich-app/immich-server:release
restart: unless-stopped
env_file: .env
volumes:
- /mnt/photos:/usr/src/app/upload
depends_on:
- redis
- database
redis:
image: redis:7
restart: unless-stopped
database:
image: tensorchord/pgvecto-rs:pg16-v0.2.0
restart: unless-stopped
env_file: .env
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Set DB_PASSWORD and DB_USERNAME in your .env. If you have a large existing photo library, do the first import with the server on a wired connection and be patient β thumbnail generation is CPU-hungry. A machine with a modest Intel N100 chip will chew through a few thousand photos overnight and be fine afterwards.
Stack 3: Monitoring, so you know before your partner does
The single most useful thing you can add to a home lab is monitoring, because it turns βthe internet is brokenβ into βthe Pi-hole container is using 4GB of RAM againβ. Prometheus plus Grafana plus node-exporter is the standard trio.
services:
prometheus:
image: prom/prometheus:v2.53.0
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prom_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana:11.1.0
restart: unless-stopped
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3000:3000"
node-exporter:
image: prom/node-exporter:v1.8.1
restart: unless-stopped
pid: host
volumes:
- /:/host:ro,rslave
volumes:
prom_data:
grafana_data:
Point Prometheus at node-exporter:9100 in its config, log into Grafana on port 3000, and import dashboard ID 1860 β that is the classic Node Exporter Full dashboard and it will immediately show you CPU, RAM, disk and network for the host. Add alerting later; visibility first.
If you want a shortcut for the fiddly parts β writing the Prometheus config, debugging why Grafana cannot reach the data source, or documenting the whole setup so future-you remembers how it works β the Home Lab & Automation Pack is a set of ready-made AI prompts for exactly this kind of scripting, troubleshooting and documentation work, from Β£9. It is the done-for-you version of the trial-and-error described in this article, and it pairs well with a stack you are already building.
Stack 4: Backups, because none of this matters otherwise
This is the stack people skip and then regret. Restic is my preference: it is fast, deduplicates well, and can push encrypted backups to cheap object storage or an external drive.
services:
restic:
image: restic/restic:0.17.0
restart: unless-stopped
environment:
- RESTIC_REPOSITORY=/backups
- RESTIC_PASSWORD=${RESTIC_PASSWORD}
volumes:
- /mnt/photos:/data/photos:ro
- /mnt/backup:/backups
entrypoint: /bin/sh
command: -c "while true; do restic backup /data; sleep 86400; done"
That loop is deliberately crude but effective: it backs up once a day and restarts with the container. For anything more sophisticated, run restic from a systemd timer on the host instead and keep Docker out of it. Whichever you choose, test a restore. An untested backup is a rumour.
On the hardware side, a decent USB 3.0 external drive is the cheapest insurance you can buy for a home lab β something like a desktop external drive works well for restic targets, and a small Raspberry Pi 5 makes a fine always-on backup host separate from your main server.
Habits that keep it running
Keep one folder per stack and one Git repository for all of them, minus the .env files. Use restart: unless-stopped on everything so a reboot does not leave you manually bringing services back. Watchtower or a monthly manual docker compose pull && docker compose up -d keeps images current without surprises. And write a plain-text README in each stack folder explaining what it does and where its data lives β your future self, standing in a cold shed at 11pm, will thank you.
None of this requires expensive kit. A quiet mini PC with 16GB of RAM and a couple of SSDs will run every stack above comfortably, and the whole point of Compose is that when you outgrow it, you copy the folders to new hardware and run the same commands. Start with the reverse proxy, add one stack at a time, and resist the urge to install everything on day one. A home lab that works is worth far more than one that merely looks impressive in a screenshot.
SEO Content Briefs with ChatGPT: A Step-by-Step Workflow
Stop staring at a blank doc. Here's a practical, repeatable workflow for building SEO content briefs with ChatGPT that writers actually want to use.
AI Prompts for Newsletters People Actually Open
Most newsletters die in the inbox graveyard because the subject line is dull and the first line is worse. Here are the exact AI prompts that fix both β plus the rest of the email.
How to Repurpose One Blog Post into 10 Pieces of Content
Write once, publish everywhere. A practical UK-focused workflow for turning a single blog post into ten distinct pieces of content, with prompts you can copy and paste.