Docker for Your Homelab: A Getting-Started Guide
A Docker homelab is a spare machine (a mini PC, an old laptop, or a Raspberry Pi) running the Docker engine so you can host your own services in containers instead of paying for cloud subscriptions. You install Docker once, describe each service in a small YAML file, and bring the whole stack up with a single command. Everything below assumes a Linux host, which is what almost every homelab ends up being.
Why containers instead of installing things directly
You could install Pi-hole, Jellyfin, and a database straight onto the host OS. People did that for years. The problem is that every app drags in its own dependencies, and eventually two of them want conflicting versions of the same library. Then an upgrade to one breaks another, and you spend a Saturday untangling it.
Containers give each service its own isolated filesystem and dependency tree. When you want to remove something, you delete the container and its image, and the host is exactly as clean as before. When you want to move the whole setup to a new box, you copy a couple of files and your data folder. That portability is the real reason a homelab lives on Docker.
If you are still deciding on a runtime, it is worth reading Docker vs Podman in 2026: An Honest Comparison before you commit. For a first homelab, plain Docker is the path of least resistance, and that is what this guide uses.
Pick your hardware
You do not need much. A machine with 4 GB of RAM and a modern-ish CPU will happily run a dozen small containers. An old office mini PC (Intel N100 or similar) is the sweet spot: low idle power, x86 so every image works, and enough headroom to grow. A Raspberry Pi 4 or 5 works too, just remember it is ARM, so occasionally an image will not have an ARM build.
Give the host a static IP on your LAN before anything else. If your router hands it a new address every week, every bookmark and reverse-proxy rule you write will break.
Install Docker
Skip the version in your distro's default repos. It is usually old. Use Docker's official convenience script or their apt repo. The quick way:
curl -fsSL https://get.docker.com | sh
Then add your user to the docker group so you are not typing sudo in front of every command:
sudo usermod -aG docker $USER
newgrp docker
Confirm it works:
docker run --rm hello-world
If that prints the welcome message, the engine is running. The --rm flag deletes the container as soon as it exits, so you do not accumulate dead containers while you experiment.
Your first real container
Let's run something you will actually use. Here is Pi-hole, a network-wide ad blocker, started with a raw docker run:
docker run -d \
--name pihole \
-p 53:53/tcp -p 53:53/udp \
-p 8080:80 \
-e TZ="America/New_York" \
-v /opt/pihole/etc:/etc/pihole \
--restart unless-stopped \
pihole/pihole:latest
A few things worth understanding here, because they apply to almost every container you will run:
-druns it in the background (detached).-p 8080:80maps port 8080 on the host to port 80 inside the container. The web UI is now athttp://your-host-ip:8080/admin.-v /opt/pihole/etc:/etc/piholemounts a host folder into the container so your config survives a restart or an image upgrade. This is the part beginners forget, and then they lose everything on the firstdocker rm.--restart unless-stoppedbrings the container back after a reboot or a crash, unless you deliberately stopped it.
Move to Docker Compose immediately
Typing long docker run lines is fine for one container. With five, it becomes a memory game you lose. Docker Compose lets you write the whole stack as one file and manage it as a unit.
Make a folder, create compose.yaml, and put your services in it:
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80"
environment:
TZ: "America/New_York"
volumes:
- ./pihole/etc:/etc/pihole
restart: unless-stopped
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
ports:
- "8096:8096"
volumes:
- ./jellyfin/config:/config
- /mnt/media:/media:ro
restart: unless-stopped
Bring it all up:
docker compose up -d
That one command pulls both images, creates a private network so the containers can talk to each other by name, and starts everything. To update later, you pull new images and recreate:
docker compose pull
docker compose up -d
Compose only recreates the containers whose images actually changed, so this is safe to run often. Keep the compose.yaml in a git repo and you have a written, versioned record of your entire homelab. That file plus your data folders is your backup.
Volumes, or where your data actually lives
Here is the mental model. The container's filesystem is disposable. When you upgrade an image, the old container is thrown away and a fresh one is built. Anything written inside the container and not on a mounted volume is gone.
So the rule is simple: every bit of state (databases, config, uploaded files) goes on a volume mapped to a host path you control. In the Compose file above, ./pihole/etc and ./jellyfin/config are exactly that. Back up those host folders and you can rebuild the whole lab from scratch.
The media library uses :ro (read-only), so Jellyfin can read your files but a compromised container can never delete them. Use read-only mounts wherever a container does not need write access.
Keeping an eye on your Docker homelab
Basic commands get you far. See what is running:
docker ps
Follow a container's logs when something misbehaves:
docker logs -f jellyfin
Check live resource use across everything:
docker stats
Add a healthcheck so Docker knows when a service is actually working, not just running:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
interval: 30s
timeout: 5s
retries: 3
The catch with a homelab is that it runs in a closet and you are not staring at a terminal. A container can crash at 2am and you would not know until someone complains the movies stopped working. This is the gap Docker HQ fills: it connects to your host over SSH and sends a push notification to your phone when a container or the host itself goes down, so you can pull up the logs and restart a service from the couch. If you want the full picture of what to track and why, Docker Container Monitoring: A Complete Guide goes deeper than a homelab strictly needs, but the fundamentals carry over.
A few habits that save you pain
Pin image versions once a service matters. :latest is convenient while you are setting up, but it means an upgrade can land whenever you run docker compose pull. For anything you depend on, use a real tag like postgres:16 so upgrades are a deliberate edit to the file.
Prune junk periodically. Old images and stopped containers eat disk quietly:
docker system prune -a
Run that with care, it removes all unused images, not just dangling ones. Read the prompt before you confirm.
Watch your image sizes if you build your own. Bloated images waste disk and slow every pull, and on a Pi that matters more than you would think. The techniques in How to Reduce Docker Image Size apply directly.
Frequently asked questions
How much RAM do I need for a Docker homelab?
4 GB is a comfortable starting point for a handful of light services like Pi-hole, a DNS resolver, and a couple of web apps. Media transcoding (Jellyfin, Plex) and databases push you toward 8 GB or more. RAM is the resource you run out of first, so buy a bit more than you think you need.
Should I use Docker or a full VM for my homelab?
Containers for services, VMs for whole operating systems. If you want to run a self-contained app, use Docker. If you need a different OS entirely or kernel-level isolation, reach for a VM. Many people run Docker inside one VM on a Proxmox host and get both.
How do I access my homelab services from outside the house?
Don't expose ports directly to the internet. Use a VPN like WireGuard or Tailscale to reach your LAN, or put a reverse proxy (Caddy, Traefik) in front with HTTPS and authentication. Opening raw ports on your router is how homelabs end up in botnets.
What happens to my data when I update a container?
Nothing, as long as that data lives on a mounted volume. The container is replaced with a fresh one built from the new image, but the host folder you mapped in stays put and gets remounted. Data written inside the container without a volume is lost on every update.
Start with one container
Don't try to build the whole thing this weekend. Install Docker, get Pi-hole or a single web app running with a volume and a restart policy, and live with it for a week. Once you trust that it comes back after a reboot and survives an image update, add the next service to your Compose file. A homelab that grows one working container at a time is the one you still have running a year from now.