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.
# 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 machine | From the API container | |
|---|---|---|
| Address to use | localhost, or 127.0.0.1 | db — the container name |
| Port to use | The host side of the published mapping | The port inside the container, 5432 |
| Does the port need publishing? | Yes, or you cannot reach it at all | No, the network path does not involve the host |
| What localhost means there | Your machine | The API container itself |
| Breaks when | The mapping is removed or the host port changes | The 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.
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-netThink 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.
Saved in this browser only.