skip to content

questions

4

Explain what docker run -p 8080:80 does: which number is which, how the capital -P flag differs, and what has to be true of the process inside the container for the mapping to work at all.

level: juniorimportance: must knowfreq 82%

answer

  1. host:container, outside-in, host first
  2. -P = all EXPOSEd ports to random host ports
  3. EXPOSE = documentation only, feeds -P
  4. app must bind 0.0.0.0, not 127.0.0.1
  5. -p 127.0.0.1:8080:80 = local-only mapping

basics

~20 s

The form is host:container, so -p 8080:80 sends traffic arriving at host port 8080 to port 80 in the container. Capital -P publishes every EXPOSEd port to a random free host port; docker port shows the result. The container process must listen on 0.0.0.0, not 127.0.0.1, or nothing reaches it.

solid answer

~50 s

`-p 8080:80` publishes a port: traffic arriving on **host** port 8080 is forwarded to port **80 inside the container**. The left side is always the host, the right side the container — getting this backwards is the classic beginner error. You can pin the host interface too: `-p 127.0.0.1:8080:80` binds loopback only, so the mapping is not reachable from outside the machine. `-P` (capital) publishes every port the image declares with `EXPOSE`, each to a randomly chosen high host port; `docker port <container>` or `docker ps` tells you which. It is convenient for throwaway runs and unsuitable where the port must be predictable. Two things trip people up. First, `EXPOSE` in a Dockerfile publishes nothing — it is documentation plus the input to `-P`. Second, the server inside the container must bind `0.0.0.0` (all interfaces). If it binds `127.0.0.1`, it only accepts connections on the container's own loopback, and the forwarded traffic is refused no matter how correct the mapping is.

code

bash · 6 lines
bash
docker run -d --name web -p 8080:80 nginx:1.27
docker run -d --name admin -p 127.0.0.1:9000:9000 myadmin:1.0
docker run -d --name rand -P nginx:1.27

docker port rand 80
docker ps --format '{{.Names}}\t{{.Ports}}'

go deeper

for a junior

State the host:container order confidently, know -P versus -p, and know EXPOSE does not publish.

for a middle

Add the host-IP form for loopback-only publishing, the 0.0.0.0 bind requirement inside the container, and that mappings are immutable after creation.

for a senior

Frame publishing as exposure surface: publish only at the edge, bind admin ports to loopback, and prefer shared networks over publishing for internal traffic.

for a principal

Talk about publishing policy across a fleet, predictable ports for firewalling and service registration, and why ephemeral -P suits tests but not production.

## The direction of the mapping A container on a bridge network has a private IP that is not routable from outside the host. Publishing creates a path: `-p HOSTPORT:CONTAINERPORT` tells Docker to accept connections on the host and forward them into the container. Mnemonic: read it outside-in, host first. `-p 8080:80` means "the outside world knocks on 8080, the container answers on 80". The full syntax is `-p [HOST_IP:][HOSTPORT:]CONTAINERPORT[/PROTOCOL]`: - `-p 80` — container port 80 to a random host port - `-p 8080:80` — all host interfaces (0.0.0.0) to container 80 - `-p 127.0.0.1:8080:80` — host loopback only; nothing off-box can connect - `-p 8080:80/udp` — UDP instead of the default TCP - repeat the flag for multiple ports; ranges like `-p 8000-8010:8000-8010` work too Compose's `ports:` uses the same string form, while `expose:` is the documentation-only equivalent of `EXPOSE`. ## What -P does Capital `-P` (`--publish-all`) publishes every port declared by `EXPOSE` in the image, each to an ephemeral host port drawn from the kernel's ephemeral range. It is useful when several instances of the same image must run without colliding, and in test suites that read the assigned port programmatically. `docker port web 80` prints the mapping; the ports column of `docker ps` shows the same. The obvious cost is unpredictability — the port changes on every restart, so it cannot be referenced by a fixed configuration or firewall rule. ## EXPOSE is not publishing `EXPOSE 80` in a Dockerfile opens nothing. It records intent as image metadata, which humans and tools read, and it feeds `-P`. Traffic never flows because of it. Correspondingly, you can publish a port that was never EXPOSEd; Docker does not require the declaration. ## The bind address inside the container This is the single most common cause of "my mapping does not work". Every container has its own network namespace, hence its own loopback. A server that binds `127.0.0.1:80` accepts only connections arriving on the container's own loopback interface. Forwarded traffic arrives on the container's `eth0` from the bridge, so the kernel refuses it and you see `connection refused` even though `docker ps` shows a perfect mapping. Frameworks default this way surprisingly often — development servers, some Python and Node setups, database packages. The fix is inside the app: bind `0.0.0.0` (for example `--host 0.0.0.0`, `HOST=0.0.0.0`, `listen 0.0.0.0:8080`). The symmetric point holds on the host side: `-p 127.0.0.1:8080:80` deliberately restricts a mapping to local access, which is the right default for admin interfaces and databases you only need from the machine itself. ## Related essentials Publishing is not needed for container-to-container traffic on a shared user-defined network — those containers already reach each other on all ports. Publish only what genuinely needs to be reachable from the host or beyond. A host port can hold only one binding: starting a second container with the same host port fails with "port is already allocated". Host ports below 1024 additionally need privileges the daemon normally has, which is why containers usually listen high internally and are published low externally. Finally, published ports are fixed at creation. There is no `docker update` for them; changing a mapping means recreating the container, which is one reason declarative tools like Compose are preferred over long-lived hand-run containers.

  • Does EXPOSE in a Dockerfile make a port reachable from the host?
    No. EXPOSE only records metadata: it documents the port for readers and tooling and supplies the list that -P publishes. Reachability requires -p or -P at run time, and you can publish a port that was never EXPOSEd.
  • The mapping looks right in docker ps but curl to the host port gives connection refused. What is the first thing you check?
    Whether the server inside the container is bound to 0.0.0.0 rather than 127.0.0.1. A loopback-only listener rejects forwarded traffic arriving on the container's eth0. Confirm with ss -ltn inside the container; the fix is in the application's bind configuration, not in the docker flags.

saying these in an interview costs you the question

  • Reading -p 8080:80 as container 8080 to host 80
  • Believing EXPOSE alone publishes a port
  • Assuming -p is needed for containers on the same network to talk
  • Ignoring that the app must listen on 0.0.0.0 inside the container
  • Thinking a published mapping can be changed on a running container

context

open as a page

When you publish a container port on a Linux Docker host, what actually happens to a packet arriving at that host port? Describe the role of the bridge interface, the iptables NAT rules Docker installs, and the docker-proxy userland process.

level: middleimportance: should knowfreq 52%

basics

~20 s

Docker adds an iptables DNAT rule in the nat table's DOCKER chain that rewrites the destination to the container's bridge IP and port; the FORWARD chain permits it and conntrack rewrites replies. Outbound container traffic is source-NATed by a MASQUERADE rule. A small docker-proxy process covers cases NAT misses, such as loopback connections.

open as a page

A container started with -p 5432:5432 turns out to be reachable from the public internet even though the host's ufw firewall denies port 5432. Explain why the host firewall did not stop it, and how you would fix the exposure properly.

level: seniorimportance: should knowfreq 48%

basics

~20 s

ufw rules live in the INPUT chain, but published container traffic is DNATed in PREROUTING and then traverses FORWARD, so INPUT is never consulted. Fix it by publishing to loopback only (-p 127.0.0.1:5432:5432), not publishing at all and using a shared network, or adding DROP rules in the DOCKER-USER chain.

open as a page

Container A publishes port 8080 on the Docker host. Container B, attached to a user-defined bridge network, tries to reach A by connecting to the host's IP address on port 8080. Does that work, what is the term for that traffic pattern, and what should B do instead?

level: middleimportance: nice to knowfreq 34%

basics

~20 s

It usually works, going out to the host and back in through the NAT rules; that loop is called hairpin traffic. It is wasteful and fragile: it depends on the host address, loses the real source IP and breaks if the mapping changes. B should join A's network and connect to A's container port directly, with no published port involved.

open as a page