Homelab Docker Networking: Bridge, Host, Macvlan, Guide
Learn Docker networking for your homelab setup. Bridge, host, macvlan, and overlay networks explained with commands, configs, and troubleshooting tips.
Author
Marcus Chen
Key Takeaways
- Docker has four main network drivers: bridge, host, macvlan, and overlay - each suited to a different scenario
- Bridge networking is the default and works for 90% of homelab setups, but it has limitations you need to understand
- Macvlan lets containers have their own IP on your physical network - useful for services that need to look like regular devices
- Overlay networks are for multi-host Docker Swarm setups - skip them if you run everything on one machine
- Getting DNS right is the single most important thing for container-to-container communication
I've been running Docker in production for enterprise environments and in my homelab since 2018, and I've made every networking mistake you can imagine. I've had containers that couldn't talk to each other because I didn't understand bridge networks. I've exposed services accidentally because I used host networking without thinking through the implications. And I've spent way too many late nights debugging Macvlan configurations that should have taken ten minutes.
Here's the thing nobody tells you about Docker networking: it's not actually that complicated. The Docker documentation makes it look intimidating because it covers everything from network namespaces to iptables rules. But for a homelab, you really only need to understand four network drivers and one or two configuration patterns.
This guide covers what each Docker network driver does, when to use it, and how to set it up for a homelab. I'll include the exact commands and Compose configurations I use on my own setup.
How Docker Networking Actually Works
Before we get into the drivers, you need to understand one thing: Docker networking is built on Linux network namespaces. Every container gets its own network stack - its own interfaces, IP addresses, routing table, and iptables rules.
Think of a network namespace as a virtual network card that only that container can see. Docker creates a virtual Ethernet pair (veth) - one end plugs into the container's namespace, the other plugs into the Docker host's network. Traffic flows through this virtual cable.
When you type docker run -p 8080:80 nginx, Docker sets up a port mapping in iptables that forwards traffic from port 8080 on your host to port 80 inside the container. It's simple in concept, but the way Docker handles this changes depending on the network driver you choose.
If you're new to Docker entirely, I'd recommend reading my Docker for Homelabs article first - it covers the fundamentals. This guide assumes you know the basics of running containers and want to understand how they communicate.
The Four Docker Network Drivers
Docker provides four built-in network drivers. There's also a "none" driver that gives the container no network access, but you probably don't need that. The official Docker networking documentation covers all of them in exhaustive detail, but here's the practical summary for homelabbers:
| Driver | Use Case | Complexity |
|--------|----------|------------|
| Bridge | Default. Container-to-container on the same host | Low |
| Host | Maximum performance, no network isolation | Low |
| Macvlan | Containers get their own IP on your LAN | Medium |
| Overlay | Multi-host networking (Swarm) | High |
Bridge Network (The Default)
Bridge is what Docker uses when you don't specify a network. It creates a private network inside your host, assigns each container an IP in the range 172.17.0.0/16 (or similar), and containers on the same bridge can talk to each other by IP.
Docker creates a default bridge called bridge automatically. You can see it with:
docker network ls
NETWORK ID NAME DRIVER SCOPE
a7c4f9d2e1b3 bridge bridge local
Here's the first thing I learned the hard way: containers on the default bridge can't resolve each other by name. They can only talk by IP address. That's fine if you have two containers and remember that your database is at 172.17.0.3, but it's a nightmare when you have ten services.
The solution is to create your own bridge network:
docker network create my-network
Containers attached to my-network can reach each other by container name automatically. Docker has a built-in DNS server that resolves container names to IPs on user-defined bridge networks.
Here's what that looks like in a Docker Compose setup I use for nearly everything. See the Docker Compose networking reference for the full options:
version: "3.8"
services:
app:
image: my-app:latest
networks:
- internal
db:
image: postgres:16
networks:
- internal
networks:
internal:
driver: bridge
With this config, the app container can connect to db:5432 using just the service name. Docker handles DNS resolution automatically.
-p or ports: in Compose. This is actually a security feature - containers are isolated unless you explicitly open ports.
Host Network (Maximum Performance)
Host networking removes the network isolation layer completely. The container shares the host's network stack - it sees the same IP, the same ports, the same routing table. No NAT, no port mapping, no virtual Ethernet pairs.
version: "3.8"
services:
performance-app:
image: my-app:latest
network_mode: host
When to use host networking:
- Network performance tools like
iperf3or packet sniffers that need raw interface access - DNS servers like Pi-hole or AdGuard that need to bind to port 53 on the host
- Multicast protocols like mDNS or UPnP that don't work across bridge network boundaries
- Applications that need to detect the host's real IP - some video streaming and P2P tools fall into this category
The biggest risk with host mode is port conflicts. If two containers both try to use port 80 in host mode, the second one fails. There's no isolation - every port your container opens is a port on your host. I've accidentally exposed a development database this way, and trust me, you don't want that lesson.
For 95% of homelab services, bridge networking with port mapping is the better choice. Use host mode only when you have a specific reason.
I cover container security in more detail in my Docker Security Hardening article, including why host networking should be used sparingly.
Macvlan Network (Containers on Your LAN)
Macvlan is the most interesting driver for homelabbers. It gives each container its own MAC address and IP address on your physical network. From your router's perspective, the container looks like a separate device.
This is incredibly useful for services that need to be directly accessible on your network:
- Media servers (Jellyfin, Plex) that use DLNA or UPnP discovery
- DHCP servers running in containers
- Services that need to bind to specific ports used by other containers
- Applications that do network discovery or broadcast
Here's how to create a macvlan network:
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
--ip-range=192.168.1.200/28 \
-o parent=eth0 \
macvlan-net
Let me explain those options:
--subnet: Your LAN subnet--gateway: Your router's IP--ip-range: A slice of your subnet reserved for containers - I use 192.168.1.200-215 to avoid conflicts with DHCP-o parent=eth0: The physical interface to bridge onto
Then attach a container:
docker run --network macvlan-net --ip 192.168.1.200 nginx
Your container now appears on your network at 192.168.1.200. Neighbors can reach it directly.
The macvlan gotcha I discovered the hard way: The host cannot communicate with containers on a macvlan network. Not directly, anyway. If you're on your Proxmox host and try to curl 192.168.1.200, it won't work. You need to either:- Access the container from another device on the network
- Create a macvlan interface on the host using the same network
- Use the host's IP with a port mapping instead
For the second option, this script creates a bridge interface on your host so it can reach macvlan containers. The Docker macvlan driver docs have more details if you need deeper configuration:
# Create a macvlan interface on the host
ip link add macvlan-host link eth0 type macvlan mode bridge
ip addr add 192.168.1.199/32 dev macvlan-host
ip link set macvlan-host up
ip route add 192.168.1.200/28 dev macvlan-host
It's messy, I know. For most homelab services that need to be accessible, I now prefer bridge networking with port mapping. I only reach for macvlan when a service needs broadcast/multicast or a specific port that conflicts with another container.
Overlay Network (Multi-Host Communication)
Overlay networks connect containers across multiple Docker hosts. They're essential for Docker Swarm but overkill for a single-node homelab.
If you're running Docker on one machine (which is most homelabs), you don't need overlay networks. If you're running a Docker Swarm cluster, overlay is how services communicate across nodes.
I won't go deep into overlay here because it's a niche use case for homelabbers. The key thing to know: if you need it, Docker handles most of the complexity. You create the network once and attach services to it.
Port Mapping Best Practices
Port mapping is how you bridge the gap between Docker's internal networking and the outside world. Get this right, and your homelab is secure and accessible. Get it wrong, and you're opening security holes.
Here are the rules I follow:
Always specify explicit ports. Don't use-P (which publishes all ports randomly). Always use -p host_port:container_port with specific ports.
Use high ports for internal tools. I map things like Portainer to port 9000, not 80. Only web-facing services that need standard ports use 80 and 443.
Bind to localhost when possible. If only other containers on the same host need to access a service, use 127.0.0.1:host_port:
docker run -p 127.0.0.1:5432:5432 postgres:16
This makes PostgreSQL available only on the local machine. Other devices on your network can't reach it.
Run everything behind a reverse proxy. This is the single most impactful change you can make for Docker networking. Instead of mapping port 80 for every web service, map each service to a high port and put a reverse proxy in front of them.My setup uses Traefik, but I've written about the best reverse proxy options in detail. All of them integrate with Docker's networking so your services get automatic SSL certificates and clean URLs.
DNS and Service Discovery
This is where most homelab Docker setups fall apart. You set up a web app and a database, connect them to the same network, and... nothing works by name. Why?
As I mentioned, containers on user-defined bridge networks get automatic DNS resolution. But there are three common gotchas:
1. Only user-defined bridge networks have DNS. The defaultbridge network does not. Always create a custom network.
2. Compose project names affect hostnames. If your Compose file defines a service called api, the hostname inside the network is api. But if you have two Compose projects running the same service name, Docker scopes them by project. The full hostname is api_project-name.
3. External DNS doesn't know about container names. If you need to access a container by name from outside Docker (e.g., from your desktop), you need a real DNS setup. I use Pi-hole with local DNS records, which I covered in my Homelab DNS guide.
For container-to-container communication, user-defined bridge networks with Compose is all you need. It's one of the reasons I recommend Docker Compose best practices for every homelab setup.
Docker Compose Networking Patterns
These are the three network patterns I use in my homelab. Pick the one that fits your setup.
Pattern 1: Single Internal Network (Simplest)
Everything on one bridge network. Services access each other by name.
version: "3.8"
services:
web:
image: nginx
ports:
- "8080:80"
networks:
- app-network
api:
image: my-api
networks:
- app-network
db:
image: postgres:16
networks:
- app-network
networks:
app-network:
driver: bridge
Use this for small setups with 3-5 services. It's simple and works.
Pattern 2: Frontend-Backend Separation (Moderate)
Separate networks for public-facing and internal services. The frontend can reach the backend, but not the other way around (though Docker doesn't enforce directionality - this is more about naming and organization).
version: "3.8"
services:
frontend:
image: nginx
ports:
- "80:80"
networks:
- frontend
backend:
image: my-api
networks:
- frontend
- backend
db:
image: postgres:16
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
Pattern 3: External Network + Reverse Proxy (Advanced)
Services join an external network managed by your reverse proxy. This allows multiple Compose stacks to share a single entry point.
Create the network first:
docker network create proxy
Then configure each service stack:
version: "3.8"
services:
my-app:
image: my-app
networks:
- proxy
- internal
db:
image: postgres:16
networks:
- internal
networks:
proxy:
external: true
name: proxy
internal:
driver: bridge
This is the pattern I use for all my services. Traefik sits on the proxy network and routes traffic to services that also join it. Internal databases stay on their own internal network, unreachable from the outside.
Troubleshooting Docker Networking
Even when you follow best practices, things go wrong. Here are the three most common issues I see, and how to fix them.
"Container can't reach the internet"
If your container can't reach external services (like an apt repository), check iptables:
# Check if Docker's iptables rules are present
iptables -L -n | grep DOCKER
# Verify the container's DNS
docker exec my-container cat /etc/resolv.conf
Common fix: restart Docker, which resets iptables rules that might have been overwritten by other software.
sudo systemctl restart docker
If that doesn't work, check whether your host has a firewall blocking masquerade traffic:
# Check if IP forwarding is enabled
sysctl net.ipv4.ip_forward
# Should return 1. If not:
sudo sysctl -w net.ipv4.ip_forward=1
"Containers can't resolve each other by name"
This almost always means you're using the default bridge network. Create a user-defined bridge network and attach containers to it. Docker's internal DNS only works on user-defined networks.
Check which network your container is on:
docker inspect my-container --format='{{json .NetworkSettings.Networks}}'
If you see "bridge" (with no network name), you're on the default bridge. Move to a custom network.
"Published port doesn't work"
Port forwarding depends on iptables rules staying intact. If you've changed firewall rules or restarted a firewall service, Docker's rules may have been wiped.
# Recreate Docker's iptables rules
sudo systemctl restart docker
If port forwarding works from the host but not from another machine, check your host's firewall:
# Check if ufw is blocking the port
sudo ufw status
# Or check firewalld
sudo firewall-cmd --list-all
Recommended Gear for Docker Networking
If you're building a homelab around Docker, the hardware you choose for your Docker host makes a big difference for networking performance. Here's what I use and recommend.
For the Docker host itself, a mini PC with at least 16GB RAM and a modern CPU handles networking overhead easily. I run my Docker setup on a machine similar to the Beelink SER5 MAX - it has enough CPU for handling bridge network traffic and port mapping without breaking a sweat.
For your network infrastructure, a good router and switch matter more than people think. Docker containers generate a surprising amount of network metadata traffic (DNS lookups, health checks, overlay encryption). I use a Ubiquiti Cloud Gateway Ultra which gives me visibility into which containers are using bandwidth and lets me set up VLANs for container network isolation.
And don't forget a UPS. A power drop during a Docker network operation can corrupt bridge network state. The APC Back-UPS Pro BX1500M keeps my networking gear and Docker host running cleanly through power fluctuations.
Frequently Asked Questions
What is the default Docker network mode, and should I use it?
The default is bridge networking. It works for basic setups, but the default bridge doesn't support automatic DNS resolution between containers. Always create a user-defined bridge network instead - it gives you DNS by container name, better isolation, and you can attach and detach containers at runtime.
Can I give my Docker container a static IP address?
Yes. On a user-defined bridge network, you can specify an IP when you create the container: docker run --network my-net --ip 172.20.0.10 nginx. In Compose, use the ipv4_address setting under the service's networks config. Just make sure the IP is within your network's subnet range.
Should I use host or bridge networking for my homelab?
Bridge, unless you have a specific reason to use host. Host networking removes network isolation and causes port conflicts. Use it only for DNS servers that need port 53, tools that need raw interface access, or multicast-dependent services. For everything else, bridge with port mapping is safer and more flexible.
How do I connect Docker containers across multiple machines?
You need an overlay network, which requires initializing Docker Swarm mode. Run docker swarm init on the manager node, join worker nodes, then create an overlay network with docker network create -d overlay my-overlay. This only makes sense if you genuinely need multi-host orchestration - most homelabs don't.
Why can't my host access containers on a macvlan network?
By design, the Docker host can't directly communicate with macvlan containers. The macvlan driver doesn't create a bridge back to the host's interface. You have three workarounds: access the container from another device on your network, create a secondary macvlan interface on the host connected to the same parent interface, or use bridge networking with port mapping instead.
The Bottom Line
Docker networking doesn't have to be complicated. For almost every homelab setup, here's what you do:
- Create a user-defined bridge network for your containers
- Use Docker Compose to define services and their networks together
- Put a reverse proxy in front of web services
- Map specific ports, never use
-P - Use host networking only when you absolutely need it
That's it. You don't need overlay networks, macvlan for every service, or deep iptables knowledge. Start simple, and only add complexity when your setup specifically requires it.
My homelab runs about thirty containers across five Compose stacks, all on bridge networks behind Traefik. It's been running for over a year without a single networking-related outage. If you follow the patterns in this guide, yours will too.
As an Amazon Associate, we earn from qualifying purchases.
