Skip to main content
ANVISoftware Solutions
Lesson 11 of 16Intermediate16 min

Networks

By the end of this lesson

Let containers reach each other by name.

Each container gets its own network namespace and an address on a Docker network. A network here is a virtual switch on the host that containers attach to, so containers on the same one can talk to each other directly.

Create your own network and you get something extra: name resolution. Docker runs a resolver for user-defined networks that maps container names to their addresses. The API container asks for db, and Docker answers with the address of the container named db.

That is why a connection string inside a container reads Host=db rather than a numeric address. Addresses change every time a container is recreated. The name does not.

Two containers, one network, connected by name
Shell
# A network of your own — this is what brings name resolution
docker network create employees-net

# The database. No published port: only the API needs to reach it
docker run -d --name db --network employees-net \
  -v employees-db-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=local-dev-placeholder \
  -e POSTGRES_DB=employees \
  postgres:16

# The API, on the same network, addressing the database by container name
docker run -d --name employees-api --network employees-net -p 8080:8080 \
  -e "ConnectionStrings__Employees=Host=db;Port=5432;Database=employees;Username=postgres;Password=local-dev-placeholder" \
  anvi/employees-api:1.4.0

# Prove the name resolves from inside the API container
docker exec employees-api getent hosts db

# Who is attached, and with which addresses
docker network inspect employees-net
  • docker network create makes a user-defined bridge network. The default bridge that containers join automatically does not resolve container names, which is the single most useful reason to create your own.
  • The database has no -p. It does not need one — traffic between containers on a shared network never goes through the host's ports. Publishing 5432 would only be for tools running on your machine.
  • Host=db in the connection string is the container name. Docker resolves it on this network, so the API does not need to know or care what address the database ended up with.
  • getent hosts db prints the resolved address from inside the container. It separates two questions that otherwise get confused: is the name resolving, and is the database accepting connections?
  • docker network inspect lists the attached containers. If a container is missing from that list, it was started without --network and is on the default bridge instead.

The same database, reached from two places, needs two different addresses:

 From your machineFrom the API container
Address to uselocalhost, or 127.0.0.1db — the container name
Port to useThe host side of the published mappingThe port inside the container, 5432
Does the port need publishing?Yes, or you cannot reach it at allNo, the network path does not involve the host
What localhost means thereYour machineThe API container itself
Breaks whenThe mapping is removed or the host port changesThe container is renamed, or the two are on different networks

The network modes worth knowing, with the honest summary of each:

User-defined bridge
What you get from docker network create. Container-name resolution, and only the containers you attach can see each other. The normal choice.
Default bridge
Where containers land when you do not specify a network. Containers can reach each other by address but not by name, which makes it awkward for anything multi-container.
Host network
The container shares the host's network namespace. No mapping needed and no network isolation either, so a port conflict with the host becomes possible. Linux only.
None
No networking at all. Appropriate for a container that processes local files and should not be able to make connections.

A container can be attached to more than one network, and that is how you keep services apart deliberately. Put the frontend and the API on one network, the API and the database on another, and the frontend has no network path to the database at all. It is a boundary the configuration enforces rather than one everybody agrees to respect.

Two published ports on the host and two containers on a shared network are separate paths to the same service. Understanding which path a request is taking answers most connectivity questions before you start changing anything.

Summary

  • Containers on a user-defined network reach each other by container name
  • The default bridge network does not resolve container names, which makes it a poor fit for multi-container setups
  • localhost inside a container is that container, so it never refers to another service
  • Container-to-container traffic does not need published ports; publishing is for access from the host
  • Separate networks are a practical way to stop one service from reaching another at all

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Make name resolution fail, then work

Start a database container and an API container without creating a network, so both land on the default bridge. From the API container, try to resolve the database's name.

Then create a network, restart both containers attached to it, and try again.

Show solution

On the default bridge the name does not resolve, so the API cannot find the database even though both containers are running on the same host. On a user-defined network the name resolves to the database's address.

Separating the two tests is the useful part. A name that will not resolve and a database that will not accept connections produce similar-looking failures in application logs, and they need completely different fixes.

Shell
docker run -d --name db -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker run -d --name api alpine:3.20 sleep 600
docker exec api getent hosts db          # no result

docker network create employees-net
docker rm -f db api
docker run -d --name db --network employees-net \
  -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker run -d --name api --network employees-net alpine:3.20 sleep 600
docker exec api getent hosts db          # resolves

docker rm -f db api && docker network rm employees-net

Think about it

Think about it

The database container in this lesson has no published port. A colleague suggests publishing 5432 so they can connect with a database client.

What does that change, and what would you suggest instead?

Show solution

Publishing the port makes the database reachable from the host, and by default from anything that can reach the host on that port. The API never needed it, so this is access added for people rather than for the application.

For local development that is a reasonable trade, with one adjustment: publish to 127.0.0.1 only, so the database is reachable from that machine and not from the surrounding network.

In a shared or production environment the answer is different. Keep the database off any published port, and reach it through a path that is authenticated and audited rather than open on a host.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

An API container and a container named db are on the same user-defined network. What host should the API's connection string use?
Why does the database container not need -p 5432:5432 for the API container to connect to it?

Saved in this browser only.