Data and networking
Publish ports deliberately
Distinguish a process listening inside a container from a host port published to local or external clients.
The Notes API listens on port 3000 inside its container. That’s a fact about the container’s network. From your machine, port 3000 is not reachable until you publish it.
Let’s run the image we built:
docker run --name notes -d -p 127.0.0.1:8080:3000 notes-api:dev
docker port notes
curl http://localhost:8080
docker port prints the mapping, 3000/tcp -> 127.0.0.1:8080. And curl returns the notes JSON.
Reading the -p flag
-p 127.0.0.1:8080:3000 has three parts, left to right: the host address, the host port, the container port. Traffic that arrives at 127.0.0.1:8080 on your machine is forwarded to port 3000 in the container.
The two ports don’t need to match. The app keeps listening on 3000, and you pick any free host port. That’s how you run two copies of the API side by side: both listen on 3000 inside, one is published on 8080 and the other on 8081.
If you write -p 8080:3000 without a host address, Docker publishes on all interfaces (0.0.0.0). Read on for why I don’t do that by default.
EXPOSE is not -p
The Dockerfile has EXPOSE 3000. That did nothing here. EXPOSE is a note in the image metadata saying “the app uses this port”. Publishing is a runtime decision you make with -p, and Docker never publishes anything on its own.
So don’t read the Dockerfile to know what’s reachable. Ask Docker with docker port.
The host address controls exposure
This is the part worth being careful about.
127.0.0.1:8080 means only programs on your machine can reach the API. 0.0.0.0:8080 means anything that can reach your machine can reach it too: other devices on your Wi-Fi, and, on a cloud server with a public IP, the whole internet. Docker on Linux also adds its own firewall rules, which can bypass the ones you set up with ufw. People have exposed databases this way without noticing.
My defaults: loopback in development, and no published port at all for services that only other containers use. Your PostgreSQL container doesn’t need a host port. The API reaches it over Docker’s network, which is the next lesson.
Try this: run the API with the loopback mapping and check it with docker port. Then stop it and run it again with -p 127.0.0.1:9090:3000. The image didn’t change, the port did. That’s the point of keeping the port outside the image.
Lesson completed