Virtualization10 min read

How to Build a Containerized Homelab with Docker

Docker is the soul of any modern homelab. Learn how to use Docker Compose to build a fully featured homelab running Pi-hole, Home Assistant, Nextcloud, WireGuard, and other popular self-hosted services.

1. Why Containerize Your Homelab?

Containerization with <strong class="text-white">Docker</strong> is the most efficient way to run multiple services on a homelab. Instead of installing each service directly on your OS (which leads to dependency conflicts, version hell, and messy cleanup), each service runs in its own isolated container with exactly the dependencies it needs.

Docker Compose lets you define your entire Homelab as a <code class="text-green-400">docker-compose.yml</code> file. One command brings up your entire Homelab; one command shuts it down cleanly. Moving to a new server? Copy the compose file, run <code class="text-green-400">docker compose up -d</code>, and everything is running in minutes.

🔄 Isolation

Each service is isolated. Upgrading Home Assistant won't break your Pi-hole.

📦 Clean removal

docker compose down removes everything. No leftover config files, no uninstall scripts.

💾 Version control

Your entire Homelab config lives in one YAML file. Put it in Git and track changes.

🔄 Reproducible

Same config on your dev machine, test server, and production Homelab.

2. Docker Compose Setup

Assuming you already have Docker installed (from the Build From Scratch guide), set up a clean directory structure:

1

Create a homelab directory

Organize your Homelab config in one place.

2

Create service directories

Each service gets its own subdirectory for config files.

3

Write docker-compose.yml

Define all your services in one YAML file.

mkdir -p ~/homelab/{pi-hole,nginx-proxy-manager,wireguard,home-assistant,portainer} cd ~/homelab

3. Docker Networking

Docker creates isolated networks for your containers. By default, containers on the same Docker network can talk to each other by container name. Containers not on the same network are isolated.

For a Homelab, use a <strong class="text-white">Docker network bridge</strong> to let your services communicate with each other, and expose only specific ports to the host.

1

Create a Docker network

Create a shared network for your Homelab services.

2

Attach containers to the network

Each service in docker-compose.yml connects to this network.

# Create a Docker network for homelab services docker network create homelab-net # Or in docker-compose.yml (auto-creates the network) services: pihole: networks: - homelab-net networks: homelab-net: name: homelab-net

4. Essential Homelab Services to Containerize

Here are the most popular containerized Homelab services, with one-line Docker Compose examples:

🛡️ Pi-hole — Network-wide Ad Blocking

Block ads and trackers at the DNS level for every device on your network.

🏠 Home Assistant — Home Automation

Control smart home devices, automate routines, and monitor sensors — all locally.

🔐 WireGuard — VPN Server

Access your entire Homelab network from anywhere. More secure than port forwarding.

📂 Nextcloud — Personal Cloud

Google Drive / Dropbox alternative. File sync, photo backup, notes, and calendar.

🌐 Nginx Proxy Manager — Reverse Proxy

Manage HTTPS certificates and route external traffic to internal services.

📊 Uptime Kuma — Monitoring

Monitor service uptime with beautiful dashboards and instant alerts.

🖥️ Portainer — Docker Management UI

Web UI to manage containers, images, networks, and volumes without CLI.

5. Complete docker-compose.yml Example

A production-ready Docker Compose setup for a Homelab with the most essential services. Save this as ~/homelab/docker-compose.yml:

version: '3.8' services: # ── DNS & Ad Blocking ────────────────────────────────── pihole: image: pihole/pihole:latest container_name: pihole environment: TZ: 'Asia/Shanghai' WEBPASSWORD: 'YourSecurePassword123' DNSMASQ_LISTENING: 'all' volumes: - ./pi-hole/etc-pihole:/etc/pihole - ./pi-hole/etc-dnsmasq:/etc/dnsmasq.d ports: - "53:53/tcp" - "53:53/udp" - "80:80/tcp" # Admin UI (restrict access via reverse proxy) networks: - homelab restart: unless-stopped dns: - 127.0.0.1 - 1.1.1.1 # ── Reverse Proxy ────────────────────────────────────── nginx-proxy-manager: image: jc21/nginx-proxy-manager:latest container_name: npm ports: - "80:80" - "443:443" - "81:81" # Admin UI environment: DB_MYSQL_HOST: "npm_db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "npm_password" DB_MYSQL_NAME: "npm" volumes: - ./nginx-proxy-manager/data:/data - ./nginx-proxy-manager/letsencrypt:/etc/letsencrypt networks: - homelab restart: unless-stopped npm_db: image: jc21/mariadb:latest container_name: npm_db environment: MYSQL_ROOT_PASSWORD: "root_password" MYSQL_DATABASE: "npm" MYSQL_USER: "npm" MYSQL_PASSWORD: "npm_password" volumes: - ./nginx-proxy-manager/db:/var/lib/mysql networks: - homelab restart: unless-stopped depends_on: - nginx-proxy-manager # ── VPN ──────────────────────────────────────────────── wireguard: image: linuxserver/wireguard container_name: wireguard cap_add: - NET_ADMIN - SYS_MODULE environment: PUID: "1000" PGID: "1000" TZ: "Asia/Shanghai" SERVERURL: "your-domain-or-ip" SERVERPORT: "51820" PEERS: "3" PEERDNS: "10.8.0.1" INTERNAL_SUBNET: "10.8.0.0" volumes: - ./wireguard/config:/config - /lib/modules:/lib/modules:ro ports: - "51820:51820/udp" networks: - homelab restart: unless-stopped sysctls: - net.ipv4.conf.all.src_valid_mark=1 # ── Monitoring ───────────────────────────────────────── uptime-kuma: image: louislam/uptime-kuma:latest container_name: uptime-kuma ports: - "3001:3001" volumes: - ./uptime-kuma:/app/data networks: - homelab restart: unless-stopped # ── Docker Management ────────────────────────────────── portainer: image: portainer/portainer-ce:latest container_name: portainer ports: - "9000:9000" - "9443:9443" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./portainer/data:/data networks: - homelab restart: unless-stopped networks: homelab: name: homelab driver: bridge

6. Backup & Maintenance

📁 Backup volumes regularly

Docker volumes persist data. Back them up to your NAS or an external drive with a cron job. Services like Portainer have built-in volume backup.

🔄 Watchtower for auto-updates

Add Watchtower to automatically pull the latest container images and restart services. Or update manually: docker compose pull && docker compose up -d.

📊 Monitor disk space

Docker images, volumes, and logs consume disk space. Run docker system df periodically and docker system prune to clean up.

📝 Keep your compose file in Git

Put your docker-compose.yml in a Git repo. Every change is tracked. On a new server, clone the repo and run docker compose up -d.

Frequently Asked Questions

Should I use Docker or Podman in my Homelab?

Docker is the standard for Homelabs — more documentation, more community support, and more one-click compose files available. Podman is daemonless and considered more secure, but Docker is simpler for beginners and works identically for Homelab use cases.

What is the difference between Docker and LXC containers?

Docker containers share the host kernel and are optimized for running applications (web servers, databases). LXC containers are more like lightweight VMs — they can run a full Linux OS with their own init system. Proxmox LXC containers are better for running multiple isolated Linux systems; Docker is better for running multiple applications on one Linux host.

How do I access my Homelab services from my phone/laptop?

Set your router's DHCP to give your Docker host a static IP (e.g., 192.168.1.100). Access services at http://192.168.1.100:port. For external access, use WireGuard VPN or Nginx Proxy Manager with a domain name and HTTPS.

Can Docker containers communicate with each other by name?

Yes — containers on the same Docker network can reach each other by service name. If you have a service named "pihole", another container can reach it at http://pihole:80. This is Docker's internal DNS.

How do I update a Docker container?

Pull the new image and recreate the container: docker compose pull && docker compose up -d. Docker Compose will automatically pull the latest image and recreate the container while preserving volumes (your data).