Reading time: ~15 minutes
Audience: Homelab beginners who have installed Linux or Proxmox
Prerequisites: A Linux VM or LXC with root/sudo access


What Is Docker Compose?

Overview

Docker Compose is a tool that lets you define and run multi-container applications using a single YAML file (docker-compose.yml). Instead of running long docker run commands with dozens of flags, you declare your entire stack — services, networks, volumes, and environment variables — in one readable file.

For homelab users, Compose is the standard way to deploy self-hosted apps like Nextcloud, Immich, Jellyfin, Pi-hole, and Portainer. A typical homelab runs 10–30 containers orchestrated through Compose files.

Key Benefits

Benefit Explanation
Declarative Define once, deploy repeatedly across machines
Version controlled Your docker-compose.yml is infrastructure-as-code
Isolated networks Services talk to each other without exposing ports to the host
Persistent volumes Data survives container deletion and updates
One-command updates docker compose pull && docker compose up -d

Prerequisites

Hardware Requirements

Setup RAM Storage Notes
Minimal (5–10 containers) 2 GB 20 GB SSD Intel N100, Raspberry Pi 5, or old laptop
Comfortable (15–30 containers) 4–8 GB 64 GB SSD Recommended for most homelabs
Heavy (30+ with databases) 16 GB+ 256 GB NVMe Include swap or ZFS compression

Software Requirements

  • Docker Engine 24.0+ and Docker Compose plugin v2.20+
  • A Linux distribution: Ubuntu 22.04/24.04 LTS, Debian 12, or Proxmox LXC (privileged or with nesting enabled)

Knowledge Prerequisites

  • Basic Linux command line (SSH, cd, ls, nano or vim)
  • Understanding of YAML indentation (spaces, not tabs)
  • A static IP or local DNS name for your server

Step 1: Install Docker and Docker Compose

Objective

Install the official Docker Engine and Compose plugin.

Step-by-Step Instructions

  1. Update your system:
sudo apt update && sudo apt upgrade -y
  1. Install required dependencies:
sudo apt install ca-certificates curl gnupg lsb-release -y
  1. Add Docker’s official GPG key and repository:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  1. Install Docker Engine and Compose:
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
  1. Add your user to the docker group (log out and back in after):
sudo usermod -aG docker $USER
newgrp docker
  1. Verify:
docker --version
docker compose version

Expected output:

Docker version 26.x.x
Docker Compose version v2.29.x

Step 2: Write Your First docker-compose.yml

Objective

Create a Compose file that runs NGINX and a simple whoami service.

Step-by-Step Instructions

  1. Create a project directory:
mkdir -p ~/compose-demo && cd ~/compose-demo
  1. Create docker-compose.yml:
version: "3.8"

services:
  nginx:
    image: nginx:alpine
    container_name: nginx-demo
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped

  whoami:
    image: traefik/whoami
    container_name: whoami-demo
    restart: unless-stopped
  1. Create a simple HTML file:
mkdir html
echo "<h1>Hello from Docker Compose!</h1>" > html/index.html
  1. Start the stack:
docker compose up -d
  1. Test:
curl http://localhost:8080

You should see “Hello from Docker Compose!”.

  1. View running containers:
docker compose ps
docker compose logs -f

Step 3: Deploy a Real Homelab Stack (NGINX Proxy Manager + Uptime Kuma)

Objective

Deploy a practical two-app stack with persistent volumes and a custom bridge network.

Step-by-Step Instructions

  1. Create a new directory:
mkdir -p ~/homelab-stack && cd ~/homelab-stack
  1. Create docker-compose.yml:
version: "3.8"

services:
  nginx-proxy-manager:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - npm-data:/data
      - npm-letsencrypt:/etc/letsencrypt
    restart: unless-stopped

  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - uptime-data:/app/data
    restart: unless-stopped
    # Access via NPM reverse proxy on port 3001

volumes:
  npm-data:
  npm-letsencrypt:
  uptime-data:
  1. Launch:
docker compose up -d
  1. Access NGINX Proxy Manager at http://your-server-ip:81. Default login:
  2. Email: [email protected]
  3. Password: changeme

  4. Access Uptime Kuma at http://your-server-ip:3001. Set it up to monitor your homelab services.

Pro Tip: Use NGINX Proxy Manager to give each app a local subdomain (e.g., npm.lan, kuma.lan) instead of remembering port numbers.


Step 4: Understanding Networks and Persistent Storage

Objective

Learn how Compose handles inter-container communication and data persistence.

Step-by-Step Instructions

Default Bridge Network: By default, Compose creates a private network. Services can reach each other by their service name as a hostname:

services:
  db:
    image: mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD: secret
  app:
    image: nextcloud
    environment:
      MYSQL_HOST: db  # Resolves to the MariaDB container

Named Volumes vs Bind Mounts:

Type Syntax Best For
Named volume volumes: app-data:/var/lib/mysql Databases, app state
Bind mount ./config:/config Config files you edit by hand
tmpfs type: tmpfs, target: /cache Ephemeral cache

Example with both:

services:
  immich:
    image: ghcr.io/immich-app/immich-server:release
    volumes:
      - immich-upload:/usr/src/app/upload          # Named volume
      - /etc/localtime:/etc/localtime:ro            # Bind mount

Step 5: Updating and Maintaining Containers

Objective

Safely update images without losing data.

Step-by-Step Instructions

  1. Navigate to your stack directory:
cd ~/homelab-stack
  1. Pull new images:
docker compose pull
  1. Recreate containers with new images:
docker compose up -d

Compose only restarts containers whose images changed. Named volumes are preserved automatically.

  1. Clean up old images to save disk space:
docker image prune -f
  1. For a full system cleanup (unused volumes, networks, images):
docker system prune -a --volumes

Warning: This deletes unused named volumes. Ensure your data is backed up.


Pro Tips

Tip 1: Use .env Files for Secrets

Never commit passwords to Git. Create .env:

DB_PASSWORD=supersecret
DOMAIN=homelab.local

Reference it in docker-compose.yml:

environment:
  MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}

Tip 2: Enable Docker Auto-Start on Boot

sudo systemctl enable docker

Your containers will start automatically after a power outage if they use restart: unless-stopped.

Tip 3: Monitor Disk Usage

Docker can consume GBs of log files. Limit log size per container:

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

Tip 4: Use docker compose -f for Multiple Files

Split your homelab into logical files:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Troubleshooting Common Issues

Permission Denied” on Bind Mounts

Docker runs as root by default. Either: - Set folder ownership to your user UID/GID - Or use named volumes instead of bind mounts

Ports Already in Use

sudo lsof -i :80

Find the conflicting service and change the host port mapping (left side):

ports:
  - "8080:80"  # Host 8080 → Container 80

Container Exits Immediately

Check logs:

docker compose logs <service-name>

Common causes: missing environment variables, wrong architecture (ARM64 vs AMD64), or port conflicts.


Conclusion

Summary

Docker Compose turns complex multi-app deployments into readable, version-controlled YAML files. In this guide, you installed Docker, wrote your first Compose file, deployed a real reverse-proxy stack, and learned how networks and volumes work.

Next Steps


Affiliate Opportunities

  • Mini PCs: Intel N100 or N305 systems for low-power Docker hosts
  • SSD Storage: Reliable SATA/NVMe drives for container volumes
  • UPS: APC Back-UPS for keeping your Docker host alive during outages

Internal Linking Strategy

  • prerequisites/proxmox-beginner-guide for readers needing a VM host
  • real-stack/portainer-setup-guide for GUI management of Compose stacks
  • conclusion/nextcloud-docker-compose as the next practical project

CTA

What are you self-hosting with Docker Compose? Share your favorite stack in the comments!

Subscribe to the WordForge newsletter for weekly Docker and homelab tutorials.