Apr 11, 2024 · Linux & Servers / Beginner · ~3 MIN READ
Docker Compose for Home Labs
Deploy services cleanly, store persistent data correctly, and upgrade without losing everything — the prerequisite for most self-hosted apps.
Who This Is For
Beginner to intermediate. This is the prerequisite article for most self-hosted app guides on this site.
Think of it like a packing list for moving house. Instead of remembering every box and where it goes, you write the list once, and unpacking looks the same no matter which new place you’re moving into.
What You’ll Build
Understand images, volumes, and networks well enough to deploy any Compose-based service cleanly, with data that survives an upgrade.
Prerequisites
- A Linux host (VM, LXC, or bare metal) with Docker Engine installed
- Basic terminal familiarity
Architecture
A predictable directory layout keeps a growing homelab sane:
Recommended Directory Structure
$ mkdir -p /opt/stacks/uptime-kuma
$ mkdir -p /opt/stacks/nextcloud
$ mkdir -p /opt/stacks/vaultwarden
Anatomy of a compose.yaml
Every service block generally needs: image, container_name, ports, environment, volumes, networks, and restart.
$ services:
$ uptime-kuma:
$ image: louislam/uptime-kuma:1
$ container_name: uptime-kuma
$ ports:
$ - "3001:3001"
$ volumes:
$ - ./data:/app/data
$ restart: unless-stopped
Named Volumes vs. Bind Mounts
- Named volume, Docker manages the storage location; simpler, portable between hosts
- Bind mount, you choose the exact host path; easier to back up directly, easier to inspect
Environment Variables and Secrets
Keep secrets in a .env file next to the compose.yaml, restrict its permissions, and never paste its contents into a screenshot or public repo.
$ chmod 600 .env
Everyday Commands
$ docker compose up -d
$ docker compose logs -f
$ docker compose pull
$ docker compose down
$ docker compose ps
The Upgrade Workflow
- Read the release notes for breaking changes
- Back up the data directory
- Pull the new image
- Recreate the stack
- Test the service actually works before moving on
Security & Backup Notes
- Application data must live in a volume or bind mount outside the container, anything written only inside the container is lost on recreate
- Never commit a
.envfile with real secrets to a public repository - Avoid the
latesttag for anything you rely on, pin a version so an upstream update doesn’t silently change behavior
Troubleshooting
- Port already in use, another service or the host itself is already bound to that port; change the host-side port mapping
- Permission denied on bind mounts, the container’s user ID often doesn’t match your host user; check the image’s documented PUID/PGID variables
- Volume mounted to the wrong path, double-check the container-side path against the image’s documentation, not just the host-side path
- Container starts but the app fails, check
docker compose logs -fbefore assuming Docker itself is broken latesttag changes behavior unexpectedly, pin to a specific version tag once a stack is stable
Lab Finish Line
A simple service (e.g. Uptime Kuma) deployed through Compose, with data in a bind mount you could back up right now.