Skip to content

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.

Written by

Richard Tucker

View all posts β†’