Prerequisites
- A Linux server (Ubuntu/Debian recommended) with a non-root user with sudo access
- Docker and Docker Compose installed (
sudo apt install docker.io docker-compose-plugin) - A domain name pointing to your server (or you can use
localhostfor testing) - Basic familiarity with the command line and text editors
Why Docker Compose for Self-Hosting?
Running multiple services on a single server is a common homelab scenario. Docker Compose lets you define your entire stack in one file and bring everything up with a single command. No more systemd service files for each app, no hunting down which port conflicts with which service.
Step 1: Create Your Project Structure
Start by creating a dedicated directory for your services:
mkdir -p ~/docker-stack/{traefik,www,apps}
cd ~/docker-stack
touch docker-compose.yml
chmod 644 docker-compose.yml
Step 2: Define Your Reverse Proxy
We’ll use Traefik as a lightweight reverse proxy. It integrates beautifully with Docker Compose and handles Let’s Encrypt automatically.
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "[email protected]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
- "--api.insecure=true"
- "--log.level=INFO"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik/letsencrypt:/letsencrypt"
networks:
- proxy
restart: unless-stopped
networks:
proxy:
driver: bridge
Step 3: Add Your First Service (Pi-hole)
Each service gets labels for Traefik routing. Here’s Pi-hole with automatic HTTPS:
pihole:
image: pihole/pihole:latest
container_name: pihole
environment:
TZ: "America/New_York"
WEBPASSWORD: "change_this_password"
volumes:
- "./apps/pihole/etc-pihole:/etc/pihole"
- "./apps/pihole/etc-dnsmasq.d:/etc/dnsmasq.d"
labels:
- "traefik.enable=true"
- "traefik.http.routers.pihole.rule=Host(`pihole.yourdomain.com`)"
- "traefik.http.routers.pihole.entrypoints=websecure"
- "traefik.http.routers.pihole.tls.certresolver=myresolver"
- "traefik.http.services.pihole.loadbalancer.server.port=80"
networks:
- proxy
- default
ports:
- "53:53/tcp"
- "53:53/udp"
- "67:67/udp"
cap_add:
- NET_ADMIN
restart: unless-stopped
Step 4: Initialize and Launch
Create the Let’s Encrypt storage and start your stack:
mkdir -p traefik/letsencrypt
touch traefik/letsencrypt/acme.json
chmod 600 traefik/letsencrypt/acme.json
docker compose up -d
Common Pitfalls & Troubleshooting
- Port 53 conflicts: Stop
systemd-resolvedor change Pi-hole’s port. - Let’s Encrypt rate limits: Test on
http://server-ip:8080first. - Permission denied on acme.json: Must be chmod 600 or Traefik won’t start.
- DNS not resolving: Verify A record points to your server’s public IP.
Going Further
Once you’re comfortable, try using .env files for sensitive data and adding healthcheck labels for automatic restarts. Docker Compose transforms self-hosting from “random commands” into “documented reproducible infrastructure.”
Leave a Reply