Docker Networking Explained

theanonymousone1 pts0 comments

Docker Networking Explained | Engineering Notes before<br>the browser paints. Runs synchronously, no defer/async.<br>--><br>Skip to content<br>Go back<br>Docker Networking Explained<br>22 May, 2026

Most of us meet Docker through one command.

docker run -p 3000:3000 my-app<br>The container starts. The app opens in the browser. Nothing feels unusual.

Then the app needs Postgres. Then Redis. Then Nginx. Then the API moves into a container and starts failing with something like:

ECONNREFUSED 127.0.0.1:5432<br>The same database URL worked from the laptop. Inside the container, it points somewhere else.

Table of contents

Open Table of contents

The Problem Docker Is Solving

Private Network Space

Bridge Networks

Published Ports

Internal Traffic

Default Bridge vs User-Defined Bridge

The Localhost Trap

Docker Compose Networking

Binding To 0.0.0.0

Outbound Traffic And Host Access

Other Network Modes

Debugging Docker Networking

Final Thoughts

The Problem Docker Is Solving

A container is isolated from the host. It has its own filesystem, process view, and usually its own network view.

But an application rarely runs as one process.

A backend service may need:

Postgres for data

Redis for caching

Nginx as an entry point

another internal API

outbound access to the internet

access to a mock server running on the laptop

Docker has to keep containers isolated while still giving them controlled ways to talk.

The result is private container networking with explicit entry points from the host.

Private Network Space

Every normal Docker container gets its own network namespace.

Inside that namespace, the container has its own interfaces, IP address, routing table, DNS config, and localhost.

localhost inside a container means that container.

It does not mean the laptop.

It does not mean another container.

Docker connects these namespaces with virtual Ethernet pairs and a Linux bridge. One end of the virtual cable goes inside the container. The other end stays on the host and connects to Docker’s bridge.

container eth0<br>virtual ethernet pair<br>Docker bridge on host<br>host network<br>The container sees a normal network interface. The host sees a virtual interface connected to Docker’s network.

Bridge Networks

Bridge networks are Docker’s default networking mechanism for single-host containers.

On many Linux setups, Docker creates a bridge interface called docker0. Containers attached to it get private IP addresses from a range like:

172.17.0.0/16<br>One container might get 172.17.0.2. Another might get 172.17.0.3.

They can talk through the bridge. Machines outside Docker cannot directly reach them unless a port is published.

Published Ports

When you run:

docker run -p 8080:80 nginx<br>the mapping is:

host port 8080 -> container port 80<br>So this request:

http://localhost:8080<br>reaches the host first. Docker forwards it into the container on port 80.

The container does not own port 8080 on the laptop. The host owns it.

The second command fails:

docker run -p 8080:80 nginx<br>docker run -p 8080:80 nginx<br>Both containers are trying to claim host port 8080.

The container ports can be the same. The host ports must be different.

Internal Traffic

Published ports are for traffic entering from outside the Docker network.

Containers on the same Docker network can talk internally using the target service’s container port.

An API container can reach Postgres on 5432 even when Postgres is not published to the host.

The app can reach the database, but the database is not exposed to everything running on the laptop or outside the server.

Default Bridge vs User-Defined Bridge

Docker ships with a default bridge network, but project containers are easier to manage on a user-defined bridge.

docker network create app-network<br>Then attach related containers to it:

docker run -d \<br>--name postgres \<br>--network app-network \<br>-e POSTGRES_PASSWORD=secret \<br>postgres:16

docker run -d \<br>--name api \<br>--network app-network \<br>-p 3000:3000 \<br>my-api<br>Now the API can reach Postgres with:

postgres:5432<br>Do not use:

localhost:5432<br>or a hardcoded container IP like:

172.17.0.2:5432<br>Use the container name.

User-defined bridge networks give containers DNS by name, cleaner isolation, and a network boundary that belongs to the project. Hardcoded IPs make deployments fragile because container IPs can change.

The Localhost Trap

Here is the bug from the opening example.

The setup:

API container<br>Postgres container<br>The API uses:

postgres://user:password@localhost:5432/app<br>Inside the API container, localhost points back to the API container itself.

The API is effectively checking whether Postgres is running inside the API container.

Postgres is in a different container, so the URL should be:

postgres://user:password@postgres:5432/app<br>where postgres is the container name or Compose service name on the same Docker network.

A useful rule:

laptop to container: localhost:published_port

container to container: service_name:container_port

container to itself:...

container docker network postgres bridge host

Related Articles