Part 1 - Building my homelab foundation (Docker, DNS, reverse proxy)
Building the foundation of my homelab
I originally built my homelab just to experiment.
I wanted to try things out, run a few services, and see what I could do with a small machine at home. Nothing serious.
That didn’t last long.
Over time, it turned into something I actually rely on. I now run multiple services and have to think about networking, structure, and reliability in a more intentional way.
Before getting into media servers, VPN tunnels, or anything exposed to the internet, I needed a solid foundation.
This post is about that foundation.
Where everything started
Before making any architectural decisions, I needed a machine to run everything.
I’m using a mini PC — a Beelink Mini S12 Pro with an Intel N100 (this will be important later), 16GB of RAM and a 500GB SSD. This thing is a beast while only drawing around 6W. It’s small, quiet, and more than capable for this setup.
I installed Debian and kept the system minimal.
One of the first things I did was configure a static lease on my router so the machine always has the same IP. Without this, everything else becomes harder than it needs to be.
Bootstrapping the environment
Once the base system was ready, I installed Docker.
From there, I added Portainer as a management layer. I still define everything using docker-compose, but Portainer makes it much easier to operate — especially when working with stacks.
From this point on, everything in my homelab runs in containers.
That decision simplified everything that came after.
From “just Plex” to needing structure
Like a lot of people, I started with a simple idea:
“I just want to run Plex at home.”
Very quickly I had multiple services running, ports everywhere, and no real structure. I was constantly trying to remember which IP and port belonged to which app.
That’s when it became clear I needed to organize things properly.
Core principle: everything runs in containers
Docker became the backbone of the setup.
Running everything in containers gives isolation between services and makes updates straightforward. If something breaks, I recreate the container instead of fixing the system.
It also makes backups and migrations predictable.
Managing containers with Portainer
Portainer sits on top of Docker as a control panel.
I mainly use it for:
- Viewing logs
- Restarting containers
- Checking container health
- Managing stacks
It’s not required, but it makes day-to-day operations much easier.
Internal DNS: more than just naming
One of the first real problems I hit was access.
Everything looked like this:
http://192.168.12.200:32400
http://192.168.12.200:8989
http://192.168.12.200:7878
It works, but it doesn’t scale — and it’s annoying.
So I introduced internal DNS.
Instead of using .lan, I moved to a proper domain structure.
Now everything looks like this:
plex.home.mydomain.com
sonarr.home.mydomain.com
radarr.home.mydomain.com
Much easier to remember, and much nicer to use.
At the same time, I wanted network-wide ad blocking. I had previously used Pi-hole on a Raspberry Pi, and it worked really well. In this setup, I consolidated everything using Technitium, which handles both DNS resolution and ad blocking for the entire network.
Here are my current blocking lists:
https://small.oisd.nl/
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/multi-onlydomains.txt
https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling/hosts
DNS stack
This is the stack responsible for internal name resolution and ad blocking.
Here’s the actual stack I’m running:
version: "3"
services:
dns-server:
container_name: dns-server
hostname: dns-server
image: technitium/dns-server:latest
# For DHCP deployments, use "host" network mode and remove all the port mappings
# network_mode: "host"
ports:
- "5380:5380/tcp" # DNS web console (HTTP)
# - "53443:53443/tcp" # DNS web console (HTTPS)
- "192.168.1.200:53:53/udp" # DNS service
- "192.168.1.200:53:53/tcp" # DNS service
- "192.168.1.200:853:853/udp" # DNS-over-QUIC service
volumes:
- config:/etc/dns
restart: always
sysctls:
- net.ipv4.ip_local_port_range=1024 65000
volumes:
config:
I bind DNS directly to port 53 on the host so it can serve the entire network.
Internal reverse proxy
DNS solves naming, but not routing.
I still didn’t like having services exposed on different ports, so I added an internal reverse proxy.
Now everything goes through a single entry point, which routes requests to the correct container.
I’m using a dedicated subdomain:
home.mydomain.com
And each service lives under it:
plex.home.mydomain.com
sonarr.home.mydomain.com
radarr.home.mydomain.com
This also allows me to use HTTPS internally.
The proxy is handled by Nginx Proxy Manager, with Let’s Encrypt certificates issued using a Cloudflare DNS challenge. I use a wildcard certificate for *.home.mydomain.com, so every service is automatically covered.
This means any new service I add is immediately available over HTTPS without needing to issue new certificates.
I also needed to add a zone in my DNS pointing home.mydomain.com to my homelab IP.
This gives me:
- A consistent access pattern
- No need to remember ports
- Proper domain names instead of local-only hostnames
- HTTPS everywhere, even inside the network
- A clean path for exposing services externally later
Proxy stack
This stack is responsible for routing traffic internally and handling HTTPS.
And here’s the proxy setup:
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
Why this structure matters
This setup might look like extra work at first, but it removes a lot of friction.
I don’t need to remember ports or IPs anymore, and everything has a clear place.
If something breaks, I rebuild from configuration instead of debugging a fragile system.
It also prepares the setup for exposing services safely later, without redesigning everything.
What’s next
With this foundation in place, everything else becomes much easier.
In the next post, I’ll walk through my Plex stack and show how I expose it to the internet without opening ports at home.
That’s where things start to get interesting.
If you’re building your own homelab, starting with a structure like this will save you a lot of time and headaches later.