
September 09, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Containers share a kernel, but they do not share an IP stack by default. Docker networking explained properly means understanding how the engine isolates workloads, assigns addresses, resolves names, and publishes ports. On a production Laravel stack or a local Laravel Sail dev environment, the wrong network choice causes hours of silent failures. This guide maps the built-in drivers, the commands you actually run, and the patterns I use on real deployments.
What is Docker networking and how does the engine wire containers together?
Docker networking is the layer that gives containers routable addresses and controlled traffic paths. Every container gets a network namespace: its own interfaces, routes, and firewall rules. The Docker daemon creates virtual bridges, veth pairs, and NAT rules on the host. Your app inside the container sees eth0; the host sees veth endpoints plugged into a Linux bridge like docker0 or a user-defined bridge.
Think of three planes. The data plane moves packets between containers and the outside world. The control plane is the Docker API and CLI creating networks and attaching endpoints. The name-resolution plane is the embedded DNS server on custom bridge and overlay networks. Mix these up and you get "connection refused" errors that look like application bugs.
I treat networking as part of application design, not an ops afterthought. On sister sites that share a Linux administration pipeline, a misconfigured publish flag or missing user-defined network has blocked deploys more than once. The fix is almost always understanding which driver and which DNS scope you chose.
The default bridge versus user-defined bridges
When Docker starts on Linux, it creates docker0, a default bridge network. Containers on it can reach the internet through NAT. They cannot resolve each other by container name unless you pass --link, which is legacy. User-defined bridge networks are the standard choice for multi-container apps. Docker runs an internal DNS server at 127.0.0.11 inside each attached container. Names match service or container names.
On Ubuntu 22.04 or 24.04, after you install Docker on Ubuntu, inspect the default setup:
docker network ls
docker network inspect bridge
ip addr show docker0 The inspect output shows subnet, gateway, and connected endpoints. That JSON is your first stop when traffic dies between two containers on the same host.
What are the main Docker network drivers and when should you use each?
Docker ships several drivers. Each one trades isolation, performance, and portability differently. Pick the driver before you bake Compose files into CI.
| Driver | Scope | Best for | Avoid when |
|---|---|---|---|
| bridge | Single host | Local dev, single-server Compose stacks | You need routable LAN IPs per container |
| host | Single host | Low-latency proxies, metrics agents | You need port isolation or multiple services on same port |
| overlay | Multi-host (Swarm) | Swarm services spanning nodes | Plain Docker Compose without Swarm |
| macvlan | Single host | Legacy apps needing real MAC/IP on LAN | Cloud VPCs with strict ENI limits |
| none | Single host | Batch jobs with no network | Any service that must call APIs or databases |
| ipvlan | Single host | Many containers on one parent interface | Your switch lacks promiscuous mode support |
For most web development workflows I maintain, bridge on a user-defined network covers local and small production hosts. Host mode appears on Traefik or nginx when every microsecond counts. Overlay enters the picture only when you adopt Swarm or need compatible patterns before Kubernetes.
The official driver reference lives in the Docker network drivers documentation. Read it once before you design a multi-tier stack.
How do you create, inspect, and connect containers to Docker networks?
The CLI surface is small but precise. Networks are first-class objects, like images and volumes.
- Create a user-defined bridge with a custom subnet if you need non-overlapping ranges:
docker network create \
--driver bridge \
--subnet 172.28.0.0/16 \
--gateway 172.28.0.1 \
app-net - Run a container attached to that network:
docker run -d --name api --network app-net \
-e DB_HOST=db \
myorg/api:latest - Attach an existing container to another network without recreating it:
docker network connect app-net legacy-worker
docker network disconnect bridge legacy-worker - Inspect connectivity and DNS entries:
docker network inspect app-net --format '{{json .Containers}}' | jq .
docker exec -it api getent hosts db In Compose, declare networks once and reference them per service. This pattern mirrors production multi-container Compose apps:
services:
web:
image: nginx:alpine
networks: [frontend]
api:
build: .
networks: [frontend, backend]
db:
image: postgres:18
networks: [backend]
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true The internal: true flag blocks outbound internet access on that network. Use it for database tiers that should never call external APIs. I use this on PostgreSQL in Docker for development when only the API tier needs egress.
Compose project names prefix network names. A project trek yields trek_backend on disk. Know that prefix when you grep docker network ls on a shared dev server.
How does container-to-container DNS and service discovery work?
Embedded DNS is the feature most developers overlook. On user-defined bridges and overlay networks, Docker runs a resolver at 127.0.0.11. Each container's /etc/resolv.conf points there. Queries for service names return the container IP on the shared network.
Resolution order matters. Docker tries network-scoped names first, then container names, then aliases you set with network_aliases in Compose. Swarm adds virtual IP load balancing for services, which behaves differently from plain container DNS.
Debugging DNS is faster than guessing firewall rules:
docker exec -it web cat /etc/resolv.conf
docker exec -it web nslookup api
docker exec -it web ping -c 2 api If nslookup fails but ping by IP works, you have a name problem, not routing. If both fail, check whether both containers share the same network. Attaching only to the default bridge breaks name discovery.
Custom DNS servers per container are possible:
docker run --dns 8.8.8.8 --dns-search corp.local myapp Compose equivalent:
services:
api:
dns:
- 8.8.8.8
dns_search:
- corp.local For deeper detail, the Docker embedded DNS documentation describes Swarm modes and round-robin behaviour.
How do you publish ports and expose containers to the host?
Containers on bridge networks sit on private subnets. Publishing maps a host port to a container port through DNAT rules. Three forms matter in daily work.
- -p 8080:80 binds host 8080 to container 80 on all interfaces (0.0.0.0).
- -p 127.0.0.1:8080:80 binds localhost only — safer on shared laptops and CI runners.
- -P publishes all exposed Dockerfile ports to random high host ports.
docker run -d -p 127.0.0.1:8080:80 --name web nginx:alpine
docker port web
ss -tlnp | grep 8080 Compose syntax mirrors the CLI:
services:
web:
ports:
- "127.0.0.1:8080:80"
expose:
- "80" expose documents ports to linked services but does not publish to the host. Use it for tiers reached only through a reverse proxy like Traefik for Docker.
Host network mode removes port mapping entirely. The container shares the host IP stack:
docker run --network host metrics-agent:latest On Linux this is literal. On Docker Desktop for macOS and Windows, host networking behaves differently — test on your target OS before relying on it.
Hairpin NAT catches people in production. A container may fail to reach another service via the host's public IP and published port. Fix it by calling the internal service name on the Docker network instead of looping through the host.
Reverse proxies and TLS termination
Production stacks rarely expose app containers directly. nginx, Caddy, or Traefik join the same user-defined network and proxy to http://api:9000. TLS terminates at the edge container. Backend tiers stay on internal networks without published ports. That layout matches how I deploy booking platforms such as Adventure Third Pole Trek behind a single entry point.
What are common Docker networking mistakes in production?
Most outages I debug are configuration, not Docker bugs. These patterns repeat across client servers and shared EC2 hosts.
Using the default bridge for multi-container apps
Containers on bridge cannot resolve each other by name. Developers hard-code IPs or use host.docker.internal hacks. Create app-net once and attach every service. Your Compose file becomes portable to staging.
Subnet collisions with VPNs and cloud VPCs
Docker picks 172.17.0.0/16 by default. Corporate VPNs often use overlapping RFC1918 ranges. Routing breaks silently. Pin explicit subnets in Compose:
networks:
app-net:
ipam:
config:
- subnet: 172.30.0.0/24 Document chosen ranges in your runbook. A JSON formatter helps when you diff inspect output from staging and production.
Forgetting firewall rules outside Docker
Docker manipulates iptables or nftables. UFW on Ubuntu can block forwarded traffic if DEFAULT_FORWARD_POLICY is DROP and Docker rules load in the wrong order. Symptom: published ports work locally but fail from another machine. Check iptables -L DOCKER -n and your host firewall together.
Running databases with published ports
Never map PostgreSQL or Redis to 0.0.0.0 on a public VPS. Keep them on internal networks. Only the application tier connects by name. If you need remote admin access, use SSH tunnels or a VPN — not wide-open 5432.
Ignoring MTU issues on overlay networks
Overlay encapsulation adds header overhead. Paths with MTU 1500 may need --opt com.docker.network.driver.mtu=1450 on cloud networks. Symptom: small HTTP requests work, large payloads hang. Compare with Kubernetes vs Docker Swarm overlay tuning if you migrate later.
Resource limits without network awareness
CPU and memory caps do not isolate network bandwidth by default. A noisy neighbour container can saturate a bridge. Combine cgroups limits with host-level monitoring. See limit Docker container resources for cgroup v2 flags that pair with sensible network segmentation.
Skipping health checks across networks
Compose healthchecks run inside the container namespace. A check that curls localhost does not prove cross-service routing works. Add a synthetic check from the web tier to the API hostname on the shared network.
For teams evaluating alternatives, Podman vs Docker migration covers rootless networking differences. CNI plugins replace Docker's bridge on Podman, but the mental model — namespace, bridge, publish — stays the same.
Key Takeaways
- Create a user-defined bridge network for every multi-container app; embedded DNS at 127.0.0.11 resolves service names only there.
- Match the driver to scope: bridge for single-host Compose, host for bare-metal performance, overlay for Swarm clusters, macvlan for LAN-visible IPs.
- Publish ports with explicit bind addresses (
127.0.0.1:8080:80) and keep databases oninternal: truenetworks without public maps. - Debug in order: shared network membership, DNS with
getent hosts, then routing withcurlby IP, then host firewall and iptables. - Pin custom subnets in Compose to avoid collisions with VPN and cloud VPC ranges.
- Terminate TLS at a reverse proxy on the same Docker network instead of exposing application containers directly.
People Also Ask
What is the difference between expose and ports in Docker Compose?
ports publishes container ports to the host with optional bind IP and protocol. expose only documents ports available to other containers on shared networks. It does not map traffic to the host. Use expose for internal tiers and ports when humans or load balancers on the host must reach the service.
Can Docker containers on different networks talk to each other?
A container attached to multiple networks can reach peers on each of them. Two containers on separate networks with no shared attachment cannot communicate until you connect one container to the other's network or introduce a router container. Multi-network attachment is a clean pattern for API tiers that sit in both frontend and backend segments.
Does Docker work with IPv6?
Yes, when you enable IPv6 on the daemon and create networks with IPv6 subnets. Support varies by host OS and cloud provider. Dual-stack setups need explicit ipam config for v6 pools. Test outbound connectivity separately — many ISP and VPS plans still treat IPv6 as optional.
How does Docker networking compare to Kubernetes networking?
Docker on a single host uses bridge NAT and embedded DNS. Kubernetes delegates pod networking to CNI plugins and uses Services for stable virtual IPs. The concepts overlap — namespaces, overlays, service discovery — but Kubernetes adds policy, ingress controllers, and cross-node routing by default. Many teams start with Compose on one VPS before adopting k8s.
Put Docker networking to work on your stack
Once Docker networking explained clicks, deploys get boring in the best way. User-defined bridges, scoped publishing, and internal tiers remove whole classes of "it works on my machine" incidents. If you are containerising a Laravel app, a booking platform, or an enterprise application, start with one Compose file, two networks, and explicit service names — then harden from there.
For related reading, see the companion piece on Docker networking and volumes, or browse the portfolio for production examples. When you want hands-on help auditing a Compose stack or migrating off legacy bridge defaults, contact us or explore support and maintenance options. You can also review API development services if your containers front a REST layer, and read more on the blog about container workflows. For background on who wrote this guide, visit about me or the home page.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

