Skip to content
Docker & Containers Pillar Guide

Docker Networking Explained for Beginners

Understand Docker networking: bridge vs host vs none, default vs user-defined bridges, container DNS by name, publishing ports, and Compose networks.

SDSysadmin Desk August 19, 2026 11 min read
Diagram-style cover showing Docker network drivers — bridge, host, and none — with containers resolving each other by name on a user-defined bridge

The moment you run more than one container, networking stops being optional. The web app has to find the database. You want to reach the app from your browser. Maybe one container should have no network at all. Docker handles all of this, but the defaults aren’t obvious, and the single most common beginner mistake — expecting containers to find each other by name on the default network — comes straight from not knowing how the pieces fit.

This guide builds the picture from the ground up. You’ll see the three network drivers you’ll actually use, why the default bridge and a user-defined bridge behave differently, how containers resolve each other by name, when you need to publish a port and when you don’t, and how Compose ties it all together so it just works.

By the end you’ll be able to look at a multi-container setup and know exactly how the traffic flows.

Every container gets a network

When Docker starts a container, it attaches it to a network. You don’t have to ask for one — there’s always a default. What changes is which network, and that choice decides three things: whether the container can reach the internet, whether it can reach other containers, and whether the outside world can reach it.

Docker ships with a handful of built-in network drivers. Three matter when you’re starting out:

The network drivers you'll actually use

bridge The default. Container gets a private IP on a virtual switch; reaches the internet via NAT; you publish ports to expose it.
host Container shares the host's network stack directly — host IP, host ports, no isolation, no -p needed. Linux-native.
none No networking at all beyond loopback. Fully isolated.

There are more — overlay for multi-host swarms, macvlan for giving containers real LAN addresses — but you can ignore those until you have a specific reason. Bridge covers the vast majority of single-host work.

List the networks Docker already has:

docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f6e5d4c3b2a1   host      host      local
1122334455aa   none      null      local

Those three are created for you and can’t be removed. Everything you make sits alongside them.

Bridge: the default, and its one big catch

When you run a container without naming a network, it joins the built-in bridge network:

docker run -d --name web nginx

That container gets a private IP (something like 172.17.0.2), can reach the internet through the host via NAT, and is reachable from the host only on ports you publish. So far so good.

Here’s the catch that trips up nearly everyone. On the default bridge network, containers can reach each other by IP address but not by name. Start a database and an app on the default bridge, point the app at db as a hostname, and it fails — there’s no DNS resolving db to an IP. People conclude Docker networking is broken when it’s working exactly as designed.

User-defined bridges: where DNS by name works

Create your own bridge network and the behavior changes in the way you actually want. Docker runs an embedded DNS server on user-defined networks, so containers resolve each other by name automatically.

Make a network:

docker network create app-net

Run two containers on it:

docker run -d --name db --network app-net postgres:16
docker run -d --name web --network app-net nginx

Now web can reach db using the hostname db, and Docker resolves it to whatever IP that container currently holds. Restart db and get a new IP — the name still resolves. No --link, no IP lookups, no hard-coded addresses.

# From inside the web container, this just works:
docker exec web ping -c1 db
How containers talk on a user-defined bridge
flowchart LR
U["Your browser"] -->|localhost:8080 published| W["web container"]
W -->|hostname: db| D["db container"]
subgraph net["user-defined bridge: app-net"]
  W
  D
end
net -.->|NAT for outbound| I["Internet"]
On a user-defined network, Docker's embedded DNS resolves container names. 'web' reaches 'db' by name; only published ports reach the host and outside world.

Host networking: no isolation, no port mapping

The host driver does away with the container’s separate network namespace. The container shares the host’s network stack directly — it uses the host’s IP, and any port it listens on is immediately a port on the host, with no -p mapping involved.

docker run -d --network host nginx

With that, Nginx is listening on the host’s port 80 directly. No -p 80:80, because there’s nothing to map — the container is on the host’s network.

This buys a little performance (no NAT layer) and is handy for tools that need to see the host’s interfaces or open many ports. The cost is isolation: the container has full reach of the host’s network, and a port conflict with a host service is now a direct clash.

None: deliberately offline

The none driver gives a container no networking beyond its own loopback interface. It can’t reach other containers, and it can’t reach the internet.

docker run --rm --network none alpine ip addr

You’ll see only lo. This is niche but real: use it for a job that should be fully sealed off — processing untrusted input, running something you don’t want phoning home, or a batch task that genuinely needs no network. Most containers never use it, but it’s good to know it exists.

Publishing ports vs talking between containers

This is the distinction that clears up most confusion, so it’s worth stating plainly. There are two completely separate kinds of communication:

Container to container, on the same network. This needs no port publishing at all. Containers on a shared network reach each other directly on the container’s own ports. If db listens on 5432, then web connects to db:5432 — done. The port is never published to the host.

Host or outside world to a container. This is what -p is for. Publishing maps a host port to a container port so traffic from your browser, or from another machine, can get in:

docker run -d --name web --network app-net -p 8080:80 nginx

Here 8080:80 means “host port 8080 forwards to container port 80.” Your browser hits localhost:8080; the container still listens on 80 internally.

The common mistake is publishing ports that don’t need to be published. A database that only your app talks to should stay on the shared network with no -p line at all. Publishing it exposes it to the host and possibly the whole LAN for no reason.

Publishing vs internal communication

Container → container (same network) No -p needed. Use the container/service name and its real port, e.g. db:5432.
Browser/host → container Publish with -p host:container, e.g. -p 8080:80.
Internal-only service (db, cache) Stay on the shared network, do not publish. Reachable by name already.
Local-only published port Bind to 127.0.0.1, e.g. -p 127.0.0.1:5432:5432, to keep it off the LAN.

Connecting and inspecting networks

You can attach a running container to another network, or detach it, without recreating it:

# Attach an existing container to a network
docker network connect app-net web

# Detach it again
docker network disconnect app-net web

A container can sit on several networks at once, which is how you isolate tiers — a web container on both a public-facing network and a private backend network, with the database only on backend.

To see exactly what’s on a network and which IPs are assigned, inspect it:

docker network inspect app-net

That prints the subnet, the gateway, and every connected container with its IP — the quickest way to confirm two containers really are on the same network when name resolution isn’t working.

How Docker Compose handles all this for you

Here’s the good news for anyone using Compose: it does the right thing automatically. When you run docker compose up, Compose creates a user-defined bridge network for the project and attaches every service to it. That’s why services reach each other by name with zero extra configuration — the embedded DNS comes free with the network Compose builds.

So in a Compose file, a web service connects to a db service using db as the hostname, because the service name is the DNS name:

services:
  web:
    image: nginx
    ports:
      - "8080:80"      # published to the host
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: change-me
    # no ports: — only 'web' needs to reach it, by name, on the shared network

Notice db has no ports: block. It doesn’t need one — web reaches it as db:5432 on the network Compose created. Only web is published, because only web needs to be reached from your browser. That’s the publishing-vs-internal rule applied in practice.

You can define extra networks explicitly when you want to separate tiers:

services:
  web:
    image: nginx
    networks: [frontend, backend]
  db:
    image: postgres:16
    networks: [backend]   # database is unreachable from the frontend network

networks:
  frontend:
  backend:

Now web straddles both networks, but db lives only on backend, so nothing on frontend can touch the database directly. The Docker Compose beginner guide walks through a full working stack if you want to see services, volumes, and networking together.

A mental model you can rely on

Docker networking, the short version

  • Every container joins a network; the default is the built-in bridge
  • Default bridge: containers reach each other by IP only — no DNS by name
  • User-defined bridge: containers resolve each other by name (use this for multi-container apps)
  • Same network = direct communication, no -p needed
  • -p is only for exposing a container to the host/outside world
  • Don't publish internal services; bind local-only ones to 127.0.0.1
  • host = share the host stack, no isolation; none = no network at all
  • Compose makes a user-defined network for you, so names just work

Wrapping up

Most of Docker networking comes down to a few rules that reinforce each other. Containers always get a network; the default bridge gives you internet access but no name resolution; a user-defined bridge adds DNS so containers find each other by name; and publishing a port is a separate concern from containers talking among themselves. Get those straight and the rest — host mode, none, multi-network isolation — slots in around them.

If you take one habit away, make it this: create a user-defined network (or let Compose do it) for anything with more than one container, and only publish the ports that genuinely need to face the outside. From here, two good next steps are putting a reverse proxy in front of your containers so several apps share one set of ports, and learning how persistent data works in the Docker volumes guide. For more, browse the Docker & Containers guides.

Frequently asked questions

What is the default Docker network?

When you run a container without specifying a network, Docker attaches it to a built-in bridge network called bridge. Containers on it can reach the outside world and can talk to each other by IP, but not by name. For name resolution between containers you need a user-defined bridge network instead.

What is the difference between bridge and host networking?

Bridge networking gives the container its own network namespace and a private IP, and you publish ports to expose it. Host networking removes that isolation — the container shares the host's network stack directly, so it uses the host's IP and ports with no -p mapping. Host is faster but offers no isolation and is Linux-only in the usual sense.

How do containers find each other by name?

On a user-defined bridge network, Docker runs an embedded DNS server that resolves container and service names to their current IP. So a container named web can reach a container named db just by using db as the hostname. This does not work on the default bridge network, which is the main reason to create your own.

Do I need to publish ports for containers to talk to each other?

No. Containers on the same network reach each other directly on the container ports, with no -p needed. Publishing with -p is only for exposing a container to the host and the outside world. A database that only your app talks to should stay unpublished on a shared network.

What does the 'none' network do?

The none driver gives a container no network access at all — only a loopback interface. It can't reach other containers or the internet. Use it for tasks that should stay fully isolated, like processing untrusted input where no network access is wanted.

Does Docker Compose create its own network?

Yes. Compose automatically creates a user-defined bridge network for your project and attaches every service to it. That's why services can reach each other by name out of the box — the embedded DNS comes with the user-defined network Compose makes for you.

Sources & further reading

Official vendor documentation referenced while writing this guide.

SD

Sysadmin Desk

Infrastructure & Cloud

Hands-on guidance for infrastructure, virtualization, and containers — Hyper-V, VMware, Docker, and the day-to-day operations work that keeps environments running.

MCSA Guru provides independent, educational IT guidance. Microsoft, Windows, Windows Server, Microsoft 365, Exchange, and Microsoft Teams are trademarks of Microsoft Corporation; Docker is a trademark of Docker, Inc. MCSA Guru is not affiliated with or endorsed by Microsoft or Docker. Always test changes in a safe environment before applying them in production.

Related guides

Fixing something right now?

Jump straight into the guide library or search for the exact error or task you are dealing with.