Docker from First PrinciplesPost 8 of 18 · 18 of 18 written
- How to use this guide
- 01The problem Docker solves
- 02What a container really is
- 03Images: layers, hashes and registries
- 04Docker's architecture
- 05Container lifecycle
- 06Dockerfiles in depth
- 07Storage: where data lives
- 08Networking
- 09Docker Compose for multi-service AI apps
- 10GPUs in containers
- 11Building good images for AI workloads
- 12Serving and production patterns
- 13Security
- 14Debugging and introspection
- 15Beyond Docker
- 16Hands-on learning path
- 17Question bank
- 18Glossary and cheat sheet
8. Networking
Each container gets its own network namespace: its own interfaces, IP address, routing table and localhost. Docker connects those namespaces to each other and to the outside world with virtual bridges, NAT rules and a built-in DNS server.
How the default bridge works
On Linux, Docker creates a virtual switch called docker0, usually on the 172.17.0.0/16 subnet. For each container it creates a veth pair, a virtual cable with two ends: one end becomes eth0 inside the container, the other plugs into the bridge. Outgoing traffic is NAT-ed (masqueraded) to the host’s address, which is how containers reach the internet.
Incoming traffic needs port publishing. -p 8000:8000 adds a destination-NAT rule: packets arriving at the host’s port 8000 are rewritten to the container’s IP and port 8000. Without -p, nothing outside the host can reach the container. docker port <name> shows the active mappings.
User-defined networks and DNS
docker network create ai-net
docker run -d --name db --network ai-net pgvector/pgvector:pg16
docker run -d --name api --network ai-net -e DATABASE_HOST=db myapiContainers on the same user-defined network resolve each other by name through Docker’s embedded DNS server (at 127.0.0.11 inside each container). The legacy default bridge has no name resolution, which is why you should always create a network, or let Compose create one. Containers on different networks can’t talk to each other at all, which is a cheap way to segment a stack.
Network drivers
| Driver | What it does | When to use it |
|---|---|---|
bridge |
Private network on one host with NAT | The default for almost everything |
host |
No network namespace; the container uses the host’s stack directly | Maximum network performance, multi-node GPU training |
none |
Only a loopback interface | Sandboxing untrusted or LLM-generated code |
overlay |
Network spanning several Docker hosts | Docker Swarm; rare outside it |
macvlan, ipvlan |
Container gets an address on the physical LAN | Special cases, appliances |
The three classic confusions
- The app binds to
127.0.0.1inside the container. Port publishing forwards to the container’seth0, which a loopback-only server never listens on. Bind to0.0.0.0:uvicorn --host 0.0.0.0,jupyter lab --ip 0.0.0.0. - One container uses
localhostto reach another.localhostis the container itself. Use the other container’s name on a shared network:postgresql://db:5432,http://qdrant:6333. - A container needs a service running on the host. Use
host.docker.internal. Docker Desktop provides it automatically; on Linux add--add-host=host.docker.internal:host-gateway. On your Mac, this is how a containerised app reaches Ollama running natively with Metal acceleration:http://host.docker.internal:11434.
Exposure and firewalls
-p 8888:8888 listens on every interface of the host, not just localhost. On Linux, Docker writes its own firewall rules, which take effect before tools like ufw, so a published port can be reachable from the internet even when ufw says it’s blocked. Jupyter, Ollama, Redis and vector databases left open this way are a common real-world compromise. Publish internal services on 127.0.0.1 (-p 127.0.0.1:8888:8888) and reach them through an SSH tunnel or an authenticated reverse proxy.
Multi-node training
Distributed training across machines uses NCCL over InfiniBand or RoCE, which needs direct access to the network hardware. Such jobs typically run with --network host, the InfiniBand devices passed in (--device /dev/infiniband), the IPC_LOCK capability and an unlimited memlock limit. In practice a scheduler (Slurm, Kubernetes) sets this up for you; Part 15 covers those.