Docker · lesson 4 of 9
Intermediate
3Also part of Docker
5- + Deploying FastAPI with Docker and Kubernetes: Images, Signals, and Probes from FastAPI
- + Dockerizing Spring Boot Applications from Java
- + Bash Process Management from Bash
- + CIS Docker Benchmark Checks with docker-bench-security from Cloud Security
- + Signing Container Images with Cosign: Verify the Digest, Not the Tag from DevSecOps (AppSec in the pipeline)
Also part of: Networking
Docker Networking
Connect containers by name on user-defined bridge networks, see network isolation, aliases, the host and none drivers, and reach the host from a container.
By SkillByExample · Updated
Every container gets its own network namespace: its own interfaces, IP address and localhost.
Docker networks decide which containers can reach each other. The examples use
nginx:1.27-alpine as a server and throwaway alpine:3.20 containers as clients.
The default bridge has no DNS
docker network ls
docker run -d --name webserver nginx:1.27-alpine
docker run --rm alpine:3.20 wget -qO- -T 2 http://webserver
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
fb23f7f3aedd bridge bridge local
012040dcda2c host host local
a6f8ee7a854a none null local
$ docker run -d --name webserver nginx:1.27-alpine
0ee8e5b10241465ffdae7974e0c9ed554e65b64464691758f8c092c0cd9ae861
$ docker run --rm alpine:3.20 wget -qO- -T 2 http://webserver
wget: bad address 'webserver'
Docker starts with three networks, one per built-in driver. Containers without --network join the
default bridge, and on that network container names do not resolve — the client could only
reach webserver by IP address, which changes every time the container is recreated.
User-defined networks resolve names
docker network create appnet
docker network connect appnet webserver
docker run --rm --network appnet alpine:3.20 wget -qO- http://webserver | grep -o '<title>.*</title>'
docker inspect --format '{{range $net, $cfg := .NetworkSettings.Networks}}{{$net}}={{$cfg.IPAddress}} {{end}}' webserver
$ docker network create appnet
a198d2577a55c1fba12b12ce02824659c5c77775d8c2923813b4b81f3031810f
$ docker network connect appnet webserver
$ docker run --rm --network appnet alpine:3.20 wget -qO- http://webserver | grep -o '<title>.*</title>'
<title>Welcome to nginx!</title>
$ docker inspect --format '{{range $net, $cfg := .NetworkSettings.Networks}}{{$net}}={{$cfg.IPAddress}} {{end}}' webserver
appnet=172.18.0.2 bridge=172.17.0.2
A network you create with docker network create (bridge driver by default) runs Docker’s embedded
DNS server at 127.0.0.11, so webserver resolves to its address on that network. docker network connect attached the running container to a second network; it now has one interface and IP
per network. Normally you just pass --network appnet to docker run.
Networks isolate containers
docker network create othernet
docker run --rm --network othernet alpine:3.20 wget -qO- -T 2 http://webserver
docker run --rm --network othernet alpine:3.20 wget -qO- -T 2 http://172.18.0.2
docker network rm othernet
$ docker network create othernet
cba06b6d02ca4ca80f1d588ed3b4e18a34a09094317f47b915909b1a9f0745e0
$ docker run --rm --network othernet alpine:3.20 wget -qO- -T 2 http://webserver
wget: bad address 'webserver'
$ docker run --rm --network othernet alpine:3.20 wget -qO- -T 2 http://172.18.0.2
wget: download timed out
$ docker network rm othernet
othernet
A container on othernet can neither resolve webserver nor reach its appnet IP address — Docker
drops traffic between bridge networks. This is how you keep, say, a database reachable from the API
but not from the public-facing proxy: put each tier on its own network and attach only the
containers that must bridge them to both.
Network aliases
docker run -d --name pg-main --network appnet --network-alias db alpine:3.20 sleep 300
docker run --rm --network appnet alpine:3.20 sh -c 'ping -c1 -W1 db | head -2'
docker rm -f pg-main
$ docker run -d --name pg-main --network appnet --network-alias db alpine:3.20 sleep 300
77755033e2725e0a9738fcd3cb970078228665766efe2058ba3e829f696ed8ec
$ docker run --rm --network appnet alpine:3.20 sh -c 'ping -c1 -W1 db | head -2'
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=1.071 ms
$ docker rm -f pg-main
pg-main
--network-alias adds an extra DNS name on that network. Clients can be configured with a stable
role name like db while the actual container (pg-main) is replaced or renamed. Docker Compose
uses the same mechanism to make every service reachable by its service name.
The none and host drivers
docker run --rm --network none alpine:3.20 ip -o -4 addr
docker run --rm --network appnet alpine:3.20 ip -o -4 addr
docker run --rm --network host alpine:3.20 ip -o -4 addr
$ docker run --rm --network none alpine:3.20 ip -o -4 addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
$ docker run --rm --network appnet alpine:3.20 ip -o -4 addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
2: eth0 inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0\ valid_lft forever preferred_lft forever
$ docker run --rm --network host alpine:3.20 ip -o -4 addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
4: eth0 inet 192.0.2.2/24 brd 192.0.2.255 scope global eth0\ valid_lft forever preferred_lft forever
5: docker0 inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0\ valid_lft forever preferred_lft forever
186: br-a198d2577a55 inet 172.18.0.1/16 brd 172.18.255.255 scope global br-a198d2577a55\ valid_lft forever preferred_lft forever
none leaves only loopback — no way in or out, for jobs that must not touch the network. A
bridge container gets one virtual eth0 on its network. host skips the network namespace
entirely: the container sees the host’s interfaces, including docker0 (the default bridge)
and br-a198d2577a55 (the bridge behind appnet). A server in a host-network container binds host
ports directly, with no -p needed — and no isolation. On Docker Desktop, “host” is the Linux VM,
not your Mac or PC.
localhost is the container, not the host
A small web server is running on the host’s port 9000 (python3 -m http.server 9000):
curl -s localhost:9000
docker run --rm alpine:3.20 wget -qO- -T 2 http://localhost:9000
docker run --rm --add-host host.docker.internal:host-gateway alpine:3.20 wget -qO- -T 2 http://host.docker.internal:9000
$ curl -s localhost:9000
hello from the host
$ docker run --rm alpine:3.20 wget -qO- -T 2 http://localhost:9000
wget: can't connect to remote host (127.0.0.1): Connection refused
$ docker run --rm --add-host host.docker.internal:host-gateway alpine:3.20 wget -qO- -T 2 http://host.docker.internal:9000
hello from the host
Inside the container, 127.0.0.1 is the container’s own loopback, where nothing listens. The
special value host-gateway makes --add-host map a name to the host’s address on the bridge
(172.17.0.1 here). Docker Desktop defines host.docker.internal for you; on Linux you add it
yourself as shown. The host service must listen on more than 127.0.0.1 for this to work.
Publish on localhost only
docker run -d --name local-only -p 127.0.0.1:8082:80 nginx:1.27-alpine
docker port local-only
docker rm -f local-only
$ docker run -d --name local-only -p 127.0.0.1:8082:80 nginx:1.27-alpine
90873a678ce05c286afef03f7f865be07dec6e6f7ab8085487f1f374a25a4e94
$ docker port local-only
80/tcp -> 127.0.0.1:8082
$ docker rm -f local-only
local-only
A plain -p 8082:80 binds 0.0.0.0, exposing the port on every host interface — and Docker’s
firewall rules can bypass a host firewall such as ufw. Prefixing the host IP limits the port to
connections from the host itself, the right default for databases and admin UIs on a dev machine.
Practice
- Print the subnet and gateway of
appnetwith a--formattemplate, then disconnectwebserverfrom it and remove both the container and the network.
Solution
docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}} gw {{.Gateway}}{{end}}' appnet
docker network disconnect appnet webserver
docker rm -f webserver
docker network rm appnet
$ docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}} gw {{.Gateway}}{{end}}' appnet
172.18.0.0/16 gw 172.18.0.1
$ docker network disconnect appnet webserver
$ docker rm -f webserver
webserver
$ docker network rm appnet
appnet
The gateway (.1) is the host’s side of the bridge. A network with containers still attached
cannot be removed.
- Create a network
shopand run nginx on it ascatalogwith two aliases,productsandinventory. Fetch the page through all three names from one client container.
Solution
docker network create shop
docker run -d --name catalog --network shop --network-alias products --network-alias inventory nginx:1.27-alpine
docker run --rm --network shop alpine:3.20 sh -c 'for h in catalog products inventory; do wget -qO- http://$h | grep -o "<title>.*</title>"; done'
docker rm -f catalog
docker network rm shop
$ docker network create shop
da00fb7fa2419dcd7b5672f8ddbff4213bf7c4571f24c0aefc78b3e0db12c7c4
$ docker run -d --name catalog --network shop --network-alias products --network-alias inventory nginx:1.27-alpine
842423b977b4209071e13e371200ad6f598ef3892100be8b94d792a12cb48ce3
$ docker run --rm --network shop alpine:3.20 sh -c 'for h in catalog products inventory; do wget -qO- http://$h | grep -o "<title>.*</title>"; done'
<title>Welcome to nginx!</title>
<title>Welcome to nginx!</title>
<title>Welcome to nginx!</title>
$ docker rm -f catalog
catalog
$ docker network rm shop
shop
- Create a network
labwith the subnet10.10.0.0/24and start a container on it with the fixed address10.10.0.50.
Solution
docker network create --subnet 10.10.0.0/24 lab
docker run --rm --network lab --ip 10.10.0.50 alpine:3.20 ip -o -4 addr show eth0
docker network rm lab
$ docker network create --subnet 10.10.0.0/24 lab
3d509c982991d20866f7b4a97901263fa4964e39e4119816338aa30b4ee272e8
$ docker run --rm --network lab --ip 10.10.0.50 alpine:3.20 ip -o -4 addr show eth0
2: eth0 inet 10.10.0.50/24 brd 10.10.0.255 scope global eth0\ valid_lft forever preferred_lft forever
$ docker network rm lab
lab
--ip only works on user-defined networks with an explicit --subnet. Prefer names over fixed IPs;
use this when something outside Docker needs a predictable address.