«Containers see each other by name? why isn’t the port forwarded? why macvlan?» — networks in Docker trip up almost every self-hosted user. Let’s review the built-in network drivers according to the official documentation and get to the point: what to choose for reverse proxy, for Pi-hole, for the monitoring stack.
Built-in drivers
Docker Engine on Linux provides several network drivers:
- bridge — the default driver for single containers on one host. Containers in the same bridge network can talk to each other, isolated from others.
- host — removes network isolation: the container uses the host’s network stack directly. Fast, but the container can “see” all host ports; convenient for things that need to see the entire network (for example, device discovery).
- none — complete isolation: the container has no network at all.
- overlay — connects multiple Docker daemons (Swarm) into a single network for cross-host communication.
- macvlan — the container gets its own MAC and appears as a separate device on your physical network (its own IP from the LAN subnet).
- ipvlan — similar to macvlan, but without separate MACs; convenient when the provider/switch limits the number of MACs, and for VLAN work.
The main fork: default bridge vs user-defined bridge
This is what trips people up most often.
- In the default bridge network (where a container lands if you don’t specify a network) containers cannot reach each other by name — only by IP. And IPs are assigned dynamically, which is a pain.
- In the user-defined bridge network (created by you) the built-in Docker DNS works (address
127.0.0.11), and containers resolve each other by container name. This is almost always what you need.
Practical takeaway: always create your own network rather than rely on the default:
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name app --network appnet myapp # reach db as host "db"
In docker-compose this happens automatically: Compose creates a separate network for the project, and services see each other by service names. So in compose you write db:5432 in the app config, not an IP.
Port forwarding
Container ports in a bridge network are accessible from the host itself, but to be visible from outside the host, you must publish the port with the -p/--publish flag:
docker run -d -p 8080:80 nginx # host:container
Security caveat: -p 8080:80 listens on all interfaces. If the service should be available only locally (for example behind a reverse proxy), bind to the loopback: -p 127.0.0.1:8080:80.
What to choose for common tasks
- Reverse proxy (Nginx Proxy Manager, Caddy, Traefik) + services: one user-defined bridge network, publish only 80/443 to the outside via the proxy, keep the rest of the services without
-p— the proxy talks to them by name. - Pi-hole/AdGuard, needing a true LAN IP: macvlan — the container gets an address in your subnet and appears on the network as a separate device.
- Monitoring/service that needs access to all host ports: host network (aware of the loss of isolation).
- Shared network namespace for two containers:
--network container:<name>— the second container shares the first’s interface and communicates through127.0.0.1(a common pattern for attaching a sidecar/VPN container).
A sober assessment
- macvlan — not a free lunch. By default the host cannot talk to its macvlan containers (isolation of the parent interface); you need a separate macvlan gateway trick. Plus not all Wi‑Fi adapters and cloud providers cope with this.
- host network breaks isolation. Saves on NAT, but the container gains access to the host’s entire network stack — use only where you consciously need it.
- Default network — source of half the “why doesn’t it resolve.” If something can’t see its neighbor by name, you’ve almost always forgotten to create your own network.
What to do
- Make user-defined bridge your rule for any multi-container stack (or simply use
docker-compose, where this is built in). - Publish externally at a minimum — ideally only 80/443 for the reverse proxy, everything else kept inside the network and bound to
127.0.0.1if publication is still needed. - macvlan — only when you truly need a LAN IP (Pi-hole/AdGuard, smart-home devices), and decide in advance the question of “host↔container.”
- Check connectivity from within a container:
docker exec -it app ping dbandgetent hosts db— you’ll immediately see whether the built-in DNS works.
Sources
How are your Docker stack networks organized — one shared network, project-level segmentation, macvlan for individual services? Have you run into “can’t resolve by name” on the default network?
