Homelab from Zero #11: Rebuild everything from one file with Docker ComposeHomelab from Zero #11: Rebuild everything from one file with Docker Compose

Homelab from Zero #11: Rebuild everything from one file with Docker Compose

This is part 11 of Homelab from Zero. In part 10 you turned one box into a platform for many. But think back over the series: every service started with a long docker run command you had to get exactly right and then remember forever. Today we replace all of them with one file.

The problem with docker run

Each service you’ve added (Heimdall, Pi-hole, Uptime Kuma, Caddy) came with its own multi-line docker run command full of ports, volumes, and flags. Those commands live nowhere except your shell history. If your server dies, or you move to the Proxmox VM from part 10, you’re reconstructing them from memory.

Docker Compose fixes this. You write your entire stack (every service, with all its ports and volumes) into a single text file called docker-compose.yml. One command reads that file and brings the whole lab up. Another tears it down. The file is your lab, written down.

Step 1: Confirm Compose is installed

Modern Docker ships Compose as a built-in subcommand. Check:

docker compose version

If that prints a version, you’re set. (If not, sudo apt install docker-compose-plugin adds it.)

Step 2: Write the Compose file

Make a folder to hold your stack and create docker-compose.yml inside it:

mkdir ~/homelab && cd ~/homelab

Here’s the whole lab, every service from the series, in one file:

docker-compose.yml
services:
heimdall:
image: lscr.io/linuxserver/heimdall:latest
container_name: heimdall
ports:
- "8080:80"
volumes:
- ~/heimdall:/config
restart: unless-stopped
pihole:
image: pihole/pihole:latest
container_name: pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8081:80"
environment:
TZ: Etc/UTC
FTLCONF_webserver_api_password: changeme
FTLCONF_dns_listeningMode: ALL
volumes:
- ~/pihole:/etc/pihole
restart: unless-stopped
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
ports:
- "3001:3001"
volumes:
- ~/uptime-kuma:/app/data
restart: unless-stopped
caddy:
image: caddy:2
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ~/caddy/Caddyfile:/etc/caddy/Caddyfile
- ~/caddy/data:/data
restart: unless-stopped

Notice the pattern: each service is a named block, and the keys map directly to the docker run flags you already know: ports is -p, volumes is -v, restart is --restart, and environment is where the old -e values go. Nothing new to learn; it’s the same settings, just written down instead of typed.

Pi-hole is the odd one out because it needs the DNS port (53) as well as its web admin page on 8081. Caddy still owns ports 80 and 443, so there’s no conflict there.

Replace changeme with the Pi-hole admin password you set back in part 4. If you leave it as-is, Compose will reset the container to that password on startup.

Step 3: Retire the old one-off containers

If you’ve been following the series, the original docker run containers are still there with the same names and ports. Compose can’t take over until you remove them:

docker stop heimdall pihole uptime-kuma caddy
docker rm heimdall pihole uptime-kuma caddy

This does not delete your data, because that lives in the folders you mapped under ~/heimdall, ~/pihole, ~/uptime-kuma, and ~/caddy.

Step 4: Bring the stack up

From the ~/homelab folder:

docker compose up -d

Compose reads the file and starts every service at once. The -d runs them in the background, exactly like docker run -d did. Check they’re all up:

docker compose ps

Step 5: The commands that replace everything

This is the payoff. The whole lab now responds to a handful of commands run from the ~/homelab folder:

Command What it does
docker compose up -d Start the stack, or recreate it after a pull
docker compose down Stop and remove the entire stack
docker compose pull Download newer images for every service
docker compose logs heimdall Read one service’s logs

Updating everything becomes two commands: docker compose pull then docker compose up -d. No more per-container juggling.

Step 6: Save the file somewhere safe

Your docker-compose.yml is now the most valuable file in your lab: it is the lab. Two things to do:

  1. Back it up. Add ~/homelab to the Restic backup from part 6.
  2. Version it. Even a private Git repo means every edit is a save point you can roll back. This is the same habit professionals use for real infrastructure.

Migrating to the Proxmox VM from part 10? Copy this one file over, restore your volume folders from backup, run docker compose up -d, and the entire lab reappears. That only works if the data folders came with it, so make sure ~/heimdall, ~/pihole, ~/uptime-kuma, and ~/caddy are in the backup set too.

What you have now

Your complete homelab defined in a single, readable, version-controlled file: one command brings it all up, one tears it down, and rebuilding on new hardware is a copy-paste away. The lab went from “a pile of commands I hope I remember” to “a document I can hand to anyone.”

In part 12 we stop treating Heimdall like a test app and turn it into the real front door to the whole lab: one dashboard, every service in one place.


← Back to blog

Following along?