Managing ports
Publishing, interfaces, ranges, and exposure.
Publishing is the one bridge between Docker’s private networks and the outside world, so it deserves precision. -p HOST:CONTAINER maps a host port to a container port using NAT; the container port is where the app listens, the host port is what the world dials. EXPOSE in a Dockerfile only documents intent — it publishes nothing. And -P (capital) auto-publishes every EXPOSE-d port to a random high host port.
$ docker run -d -p 8080:80 nginx:1.27 # host 8080 -> container 80$ docker run -d -P nginx:1.27 # EXPOSEd ports -> random host ports$ docker port <id>80/tcp -> 0.0.0.0:49154 # see what -P chose
Bind to an interface, choose a protocol
The full form is -p [HOST_IP:]HOST:CONTAINER[/proto]. Pin the host IP to control exposure: 127.0.0.1:8080:80 makes the service reachable only from the host itself, while a bare 8080:80 binds 0.0.0.0 — every interface, including public ones on a cloud VM. Append /udp for UDP services (DNS, syslog); the default is TCP. You can publish the same container port as both TCP and UDP with two -p flags.
$ docker run -d \-p 127.0.0.1:9090:9090 \ # host-only metrics port-p 53:53/udp \ # a UDP service-p 8080:80 \ # public HTTPmyimg
Publishing a range, and the internal case
You can map a contiguous range (-p 8000-8010:8000-8010) for services that use several ports, and you can map to a different host port than the container port to avoid collisions when running several copies. The most important rule, though, is the negative one: containers on the same user-defined network reach each other with no publishing at all, so publish only what genuinely must be reached from outside the host.
# two copies of the same image, no collision, both host-only$ docker run -d -p 127.0.0.1:8081:80 --name a nginx:1.27$ docker run -d -p 127.0.0.1:8082:80 --name b nginx:1.27# internal callers need neither -p: curl http://a from another container on the net