Chapter 6 of 16 · Visual guide

Ports & networking basics

Containers are isolated by default. Publishing a port is how you deliberately open one door.

key idea / Docker itselfcontainer / good pathhost / watch outproblem / blockedneutral

The wall around a container

A request from your browser only gets in if you publish the port.

Containers are walled off by defaultWithout -p the host cannot reach the container port. With -p 8080:3000, traffic to host port 8080 is forwarded to container port 3000.No published portBrowseron your laptopContainer: app on :3000unreachable from outside-p 8080:3000Browserlocalhost:8080host :8080Container: app on :3000receives the requestPublishing is opt-in: you decide which ports leave the container.

Reading -p

Reading -p-p8080host port:3000container portbrowser usesapp listenshost:8080 is forwarded to container:3000
Host port first, container port second. The two numbers are independent.

Many containers, one inner port

Port numbers inside and outside are separate namespaces.

Same inner port, different host portsThree containers all listen on 3000 internally. They are published on host ports 8001, 8002 and 8003.host :8001your laptopapp1listens on :3000host :8002your laptopapp2listens on :3000host :8003your laptopapp3listens on :3000-p 8001 twiceport in use
Inside, every container thinks it owns port 3000. The host ports must be unique.

EXPOSE versus -p

EXPOSE versus -pEXPOSE 3000documentation only: opens nothing-p 8080:3000creates the forwarding rulein the Dockerfileon docker runEXPOSE helps readers and docker run -P; it never publishes by itself.
EXPOSE is a note for humans and tools. Only -p changes what the host can reach.

The localhost trap

The localhost trapInside a container localhost means the container itself. To reach a service on your laptop use host.docker.internal.Containeryour appcalls localhost:5432nothing on :5432Your laptop (host)Postgreslistening on :5432host.docker.internallocalhost = the container itself. The laptop is host.docker.internal.

Try it yourself

  1. Step 1 · No published port

    Nginx is running, and the host still has nothing on port 8080.

    $ docker run -d --name quiet --rm nginx:1.27
    $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080
    000
  2. Step 2 · Publish host 8080 to container 80
    $ docker stop quiet
    $ docker run -d --name web --rm -p 8080:80 nginx:1.27
    $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080
    200
    $ docker port web
    80/tcp -> 0.0.0.0:8080
  3. Step 3 · A second container can also listen on 80

    Inside each container the process uses port 80. On the host the ports are 8080 and 8081.

    $ docker run -d --name web2 --rm -p 8081:80 nginx:1.27
    $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8081
    200
    $ docker stop web web2

Recap

Quick check

Your app in a container calls localhost:5432 for Postgres running on your laptop, and it fails. Why?
Show answer

localhost is the container itself, and nothing listens there. Use host.docker.internal, or put the database in a container on the same network and call it by name.