Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Docker Networking Explained

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.

Docker Networking Explained — OverviewLinux Hostdocker0default bridgeapp-netuser bridge + DNSiptablesNAT and publishwebapidbEach container = isolated network namespace
Docker networking explained: the host runs bridges, virtual interfaces, and firewall rules that connect container namespaces.

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.

DriverScopeBest forAvoid when
bridgeSingle hostLocal dev, single-server Compose stacksYou need routable LAN IPs per container
hostSingle hostLow-latency proxies, metrics agentsYou need port isolation or multiple services on same port
overlayMulti-host (Swarm)Swarm services spanning nodesPlain Docker Compose without Swarm
macvlanSingle hostLegacy apps needing real MAC/IP on LANCloud VPCs with strict ENI limits
noneSingle hostBatch jobs with no networkAny service that must call APIs or databases
ipvlanSingle hostMany containers on one parent interfaceYour 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.

Choose a Docker Network DriverNeed container networking?NoYesnonebridgehostoverlaymacvlanMulti-hostMax perfSingle host
Driver selection for Docker networking explained: start with bridge, then host, overlay, or macvlan based on scope and isolation needs.

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.

  1. 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
  1. Run a container attached to that network:
docker run -d --name api --network app-net \
  -e DB_HOST=db \
  myorg/api:latest
  1. Attach an existing container to another network without recreating it:
docker network connect app-net legacy-worker
docker network disconnect bridge legacy-worker
  1. 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.

Embedded DNS Resolutionwebcurl http://api127.0.0.11Docker DNSapi172.28.0.31 query2 A record3 TCP to resolved IP on app-netWorks only on user-defined bridge or overlayNot on default bridge without legacy links
Docker networking explained: the embedded DNS at 127.0.0.11 resolves service names to container IPs on shared networks.

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.

Production Networking GotchasAvoidDefault bridge + hard-coded IPsDB on 0.0.0.0:5432VPN subnet overlapPreferUser-defined bridge + DNSinternal: true for data tierExplicit ipam subnetsDebug checklistdocker network inspectgetent hosts + curl by IPiptables / ufw FORWARD policy
Docker networking explained: common production mistakes and the safer defaults that prevent connection failures.

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 on internal: true networks without public maps.
  • Debug in order: shared network membership, DNS with getent hosts, then routing with curl by 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

Docker networking is how the engine gives containers routable addresses and controlled traffic paths via virtual bridges, veth pairs, NAT rules, and network drivers.

The default docker0 bridge lets containers reach the internet through NAT but cannot resolve each other by container name unless you use legacy --link flags. User-defined bridge networks are the standard for multi-container apps. Docker runs an embedded DNS server at 127.0.0.11 inside each attached container, so service and container names resolve to IPs on the shared network. On real Laravel Sail or Compose stacks, attaching everything to a user-defined network like app-net prevents silent connection failures that look like application bugs.

Bridge suits single-host local dev and Compose stacks. Host shares the host IP stack for low-latency proxies but removes port isolation. Overlay spans Swarm nodes for multi-host services. Macvlan gives containers real MAC and LAN IPs for legacy apps. None disables networking for batch jobs. Ipvlan packs many containers on one parent interface when your switch supports it. For most web workflows I maintain, user-defined bridge covers local and small production hosts; overlay only enters when you adopt Swarm or need compatible patterns before Kubernetes.

On user-defined bridges and overlay networks, Docker runs a resolver at 127.0.0.11 and each container's resolv.conf points there. Queries for service names return the container IP on the shared network. Resolution tries network-scoped names first, then container names, then aliases set with network_aliases in Compose. Debug with docker exec web cat /etc/resolv.conf, nslookup api, and getent hosts db. If nslookup fails but ping by IP works, you have a name problem, not routing.

ports publishes container ports to the host with optional bind IP and protocol. expose only documents ports available to other containers on shared networks and does not map traffic to the host.

Use -p 8080:80 to bind host 8080 to container 80 on all interfaces, or -p 127.0.0.1:8080:80 for localhost-only binding, which is safer on shared laptops and CI runners. -P publishes all Dockerfile EXPOSE ports to random high host ports. Verify with docker port and ss -tlnp. In Compose, mirror the CLI with ports entries. Containers on bridge networks sit on private subnets; publishing maps host ports through DNAT rules managed by iptables or nftables.

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 with docker network connect or introduce a router container. Multi-network attachment is a clean pattern for API tiers that sit in both frontend and backend segments, matching Compose layouts where the API joins frontend and backend while the database stays backend-only.

The internal flag blocks outbound internet access on that network. Use it for database tiers that should never call external APIs. In a typical three-tier Compose file, PostgreSQL sits on a backend network marked internal: true while only the API tier on both frontend and backend networks needs egress. This keeps data stores reachable by application containers via embedded DNS without exposing them to the public internet or unnecessary outbound routes.

Using the default bridge for multi-container apps breaks name discovery. Subnet collisions with VPNs and cloud VPCs over the default 172.17.0.0/16 range break routing silently. UFW on Ubuntu can block forwarded traffic when DEFAULT_FORWARD_POLICY is DROP and Docker iptables rules load in the wrong order. Publishing PostgreSQL or Redis to 0.0.0.0 on a public VPS is dangerous. Overlay networks may need MTU tuning when large payloads hang but small requests work. I've seen misconfigured publish flags block deploys on shared EC2 hosts more than once.

Work in order: confirm both containers share the same network with docker network inspect, test DNS with docker exec api getent hosts db, then routing with curl by IP. If nslookup fails but ping by IP works, fix naming, not firewall rules. Check published ports with docker port and ss -tlnp. Inspect host firewall alongside iptables -L DOCKER -n because UFW can block forwarded traffic even when local curls succeed. Compare inspect JSON between staging and production when subnets differ.

Hairpin NAT occurs when a container tries to reach another service through the host's public IP and a published port instead of the internal Docker network name. The connection may fail even though both services run on the same host. Fix it by calling the internal service hostname on the shared user-defined network, for example http://api:9000 through a reverse proxy, rather than looping traffic out through the host's published address.

Production stacks rarely expose app containers directly. nginx, Caddy, or Traefik join the same user-defined network and proxy to internal service URLs like http://api:9000. TLS terminates at the edge container while backend tiers stay on internal networks without published ports. Use expose in Compose for tiers reached only through the proxy. That layout matches how I deploy booking platforms behind a single entry point rather than mapping application ports to 0.0.0.0.

Host mode removes port mapping entirely because the container shares the host IP stack. Use it for low-latency proxies and metrics agents where every microsecond counts. Avoid it when you need port isolation or multiple services binding the same port. On Linux, host networking is literal. On Docker Desktop for macOS and Windows, host networking behaves differently, so test on your target OS before relying on it in production.

Docker on a single host uses bridge NAT and embedded DNS at 127.0.0.11. Kubernetes delegates pod networking to CNI plugins and uses Services for stable virtual IPs. The concepts overlap around namespaces, overlays, and service discovery, but Kubernetes adds network policy, ingress controllers, and cross-node routing by default. Many teams start with Compose on one VPS before adopting Kubernetes; the mental model of namespace, bridge, and publish stays the same when migrating.

Yes, when you enable IPv6 on the daemon and create networks with IPv6 subnets. Support varies by host OS and cloud provider, and dual-stack setups need explicit ipam config for v6 pools.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: