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.
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.