← Back to the section

Running a single container is easy. But a real application almost always consists of several: a web service, a database, a cache. They need to talk to each other — and here it's important to understand how Docker organizes the network environment of containers.

Why containers need their own network

By default, each container runs in an isolated network namespace. This means the container has its own network stack: its own address, its own ports, its own loopback interface (lo). Processes inside the container don't see the host's network interfaces directly.

This isolation gives two important properties:

  • Security: a container can't accidentally grab a port belonging to the host or a neighboring container without explicit permission.
  • Predictability: the application inside the container always sees the same environment — regardless of what else is running on the host alongside it.

A short formula: a container = a separate machine on the network that Docker itself connects to the others.

The default bridge network

When you start a container with docker run and no extra flags, Docker attaches it to the default bridge network — a virtual switch named bridge (visible on the host as the docker0 interface).

docker network ls
# NETWORK ID     NAME      DRIVER    SCOPE
# abc123def456   bridge    bridge    local
# ...

Containers on the bridge network can reach each other by IP address, but not by name. Addresses are assigned dynamically on each start, which means you can't hard-code them.

Example: we start two containers and try to reach one by name:

docker run -d --name app eclipse-temurin:21-jre java -jar /app.jar
docker run -d --name db postgres:16

# From inside app you can't reach db by name —
# the name doesn't resolve on the default bridge network

This is exactly where the typical mistake lies: a developer writes localhost in the application config, expecting to reach a database in a neighboring container. But localhost inside a container is the loopback interface of the container itself, not the host. The database isn't listening there.

User-defined bridge and DNS by name

The solution is to create a user-defined bridge network. On such a network, Docker automatically brings up a built-in DNS resolver: containers reach each other by container name or service alias.

# Create the network
docker network create backend-net

# Start the database on this network
docker run -d \
  --name db \
  --network backend-net \
  postgres:16

# Start the application on the same network
docker run -d \
  --name app \
  --network backend-net \
  -e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb \
  eclipse-temurin:21-jre java -jar /app.jar

Now app can reach db by name — Docker resolves the name db to the right IP address itself. This works because user-defined networks have a built-in DNS, which the default bridge network lacks.

Advantages of a user-defined bridge over the default one

Propertybridge (default)user-defined bridge
Connectivity by IPyesyes
DNS by container namenoyes
Isolation from other networksnoyes
Ability to reconnect a containernoyes

Publishing ports to the outside: the -p flag

A container is isolated from the host. For the outside world (a browser, a client, another service outside Docker) to reach a container's port, you need to publish the port with the -p flag:

docker run -d \
  --name app \
  --network backend-net \
  -p 8080:8080 \
  eclipse-temurin:21-jre java -jar /app.jar
#   ^^^^  ^^^^
#  host  container

Format: -p <port_on_host>:<port_in_container>.

Now a request to the host's http://localhost:8080 reaches port 8080 of the container.

Important: you don't need to publish the database (db) to the outside — app reaches it over Docker's internal network, and exposing the PostgreSQL port on a public interface is a security risk.

# db stays only on the internal network — we don't forward the port outside
docker run -d \
  --name db \
  --network backend-net \
  postgres:16

Network isolation

Containers attached to different user-defined networks don't see each other — even when they run on the same host. This is handy when you need to run several independent stacks (for example, two projects) on one machine without conflicts.

docker network create project-a
docker network create project-b

# Containers in project-a and project-b are isolated from each other

A single container can be attached to several networks at once — for example, an API gateway that must talk to both the frontend and the backend:

docker network connect project-b gateway

The host and none modes

Besides bridge, Docker supports two more modes worth knowing about:

--network host — the container uses the host's network stack directly, without isolation. Useful for diagnostics or high-load cases where the bridge overhead is critical. It works only on Linux; on macOS and Windows there's no full support.

docker run --network host nginx
# Nginx listens on the host's ports directly, without -p

--network none — the container is fully isolated from the network. Used for tasks that don't need the network at all: cryptographic computations, offline data processing.

docker run --network none alpine sh

How it looks all together: the diagram

diagram

The client sees only the host's port. The database is hidden inside the Docker network — unreachable from the outside.

In short

  • Each container runs in an isolated network namespace: it has its own localhost, and it doesn't see its neighbors without explicit configuration.
  • The default bridge network provides connectivity by IP, but not by name — it isn't suitable for a production stack.
  • A user-defined bridge network is the right choice: DNS by container name, isolation, flexible connectivity.
  • Ports are published to the outside via -p host:container; internal services (database, cache) don't need to be published.
  • localhost inside a container is the container itself, not the host and not a neighboring service.
  • --network host removes isolation, --network none disables the network entirely.
  • Running containers — docker run flags, environment variables, run modes.
  • Docker Compose — a declarative description of multi-container applications; networks are created automatically.
  • Volumes and data — how to store data outside the container.