How it works
The explanation, with examples you can run.
Containers talking to containers
docker network create appnetdocker run -d --name api --network appnet hello:1 docker run --rm --network appnet alpine:3.22 wget -qO- http://api:3000/# hello from 46eeb327545b docker run --rm alpine:3.22 wget -qO- -T 2 http://api:3000/ # default bridge# wget: bad address 'api:3000' docker run --rm --network appnet alpine:3.22 cat /etc/resolv.conf# nameserver 127.0.0.11On a user-defined bridge, Docker runs an embedded DNS server (reached at 127.0.0.11 inside the container) that answers with the IPs of containers on the same network, by container name or network alias. The default bridge network has no such DNS. Compose creates a network per project for you, which is why services there can use each other's names.
Note that the container talks to api:3000, the port the app listens on inside its own namespace. Publishing is not needed for container-to-container traffic.
Publishing ports to the host
docker run -d -p 8080:3000 hello:1 # all host interfaces, port 8080 -> container 3000docker run -d -p 127.0.0.1:8080:3000 hello:1 # only the host itselfWithout an address, a published port listens on every interface of the host, so a database published on a cloud VM is open to anyone who can reach that VM. On Linux, Docker inserts its own firewall rules for published ports, and Docker's documentation warns that they are incompatible with ufw: traffic to published ports can bypass ufw's rules. Bind internal services to 127.0.0.1, or do not publish them at all and reach them over a Docker network.
A related hardening arrived in Engine 28.0: older releases let hosts on the same LAN segment reach ports published only to localhost, and even reach unpublished container ports directly; 28.0 blocks that by default.
EXPOSE 3000 in a Dockerfile publishes nothing. It records the intended port as metadata, which docker run -P (publish all exposed ports on random host ports) and humans read.
Other network modes
--network host(Linux): the container shares the host's network namespace; no isolation and no port mapping.--network none: only a loopback interface, for jobs that must not talk to anything.macvlan/ipvlan: give containers addresses on the physical network.overlay: spans several hosts (Swarm); Kubernetes uses its own CNI plugins instead.
When something cannot connect, check in order: is it on the same network, does the name resolve (getent hosts api), is the app listening on 0.0.0.0 rather than 127.0.0.1 inside its container, and, from the host, is the port published on the address you are using.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.