shouldenough

DevOpsDocker for Developers

Lesson 4 of 6

Lesson 04/14 minutes/2 graded

Ports and networking

Publishing, the difference between EXPOSE and -p, and why localhost lied to you.

A container gets its own network namespace, so it has its own interfaces and its own set of ports. Port 80 inside the container has nothing to do with port 80 on your machine, even though both are called 80.

To reach a container from your browser, you have to publish a port.

~

Run the nginx image and publish container port 80 on host port 80.

EXPOSE does not expose anything

EXPOSE 8080

EXPOSE is documentation and nothing more. It records which port the image serves on so that tooling and people can read it, and it opens nothing. A container built from that Dockerfile stays unreachable from outside until something publishes the port: -p on the command line, a ports: entry in Compose, or -P to publish every exposed port on random host ports.

The mapping is host first

docker run -p 8080:80 nginx

Host 8080 maps to container 80. Visit localhost:8080 and nginx answers. Reverse the two and the mapping is still created, so nothing warns you; the connection simply refuses, because nothing is listening on the container port you named. docker port <name> shows you what was actually published.

You can also bind to a specific interface:

docker run -p 127.0.0.1:8080:80 nginx

Now the published port is bound to loopback instead of every interface. Without the address, Docker binds 0.0.0.0, including the interface your network can see. On a laptop on shared wifi, that difference matters.

localhost inside a container is the container

A process inside a container that connects to localhost:5432 is looking for a database inside its own namespace. Your host's Postgres is not there.

The fix depends on what you are connecting to:

To reach another container, put both on the same user-defined network and use the container name as the hostname; Docker's embedded DNS resolves it. To reach a service on the host, use host.docker.internal on Docker Desktop, which on plain Linux needs --add-host=host.docker.internal:host-gateway.

docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=local-only postgres:17
docker run -d --name api --network appnet -p 3000:3000 shouldenough/api:1.0

The api container now reaches the database at db:5432. No published port is needed for that, because the traffic never leaves the network.

Check yourself

Knowledge check2 questions
Question 1 of 2
What does EXPOSE in a Dockerfile do?