

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 versionIf 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 ~/homelabHere’s the whole lab, every service from the series, in one file:
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-stoppedNotice 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 caddydocker rm heimdall pihole uptime-kuma caddyThis 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 -dCompose 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 psStep 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:
- Back it up. Add
~/homelabto the Restic backup from part 6. - 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~/caddyare 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