Docker manages networking - containers can be attached to Docker networks, and containers on the same network can reach each other by IP address.
Docker also runs a DNS server, so containers on the same network can find each other using the container name as the DNS host name.
Networks are separate objects, which you can manage from the Docker CLI:
docker network --help
docker network ls
Docker uses a plugin model for networking, so containers can be modelled to fit with different physical network architectures.
Docker networks are first-class citizens. We've been using them already, and Docker Compose creates them when we deploy an app:
📋 Run the app, list all networks and print the details of the new application network.
docker-compose -f labs/networking/compose.yml up -d
# networks are a top-level object in the Docker CLI:
docker network ls
# compose adds the project name as a prefix to the network name:
docker network inspect networking_app-net
You'll see a lot of details about the network - including containers which are attached to it. The API and web containers are there, with IP addresses in the same network range so they should are able to connect.
The web container image has some networking tools installed, which we can use for troubleshooting:
nslookup
to query the DNS server and get an IP addressping
to test the network connection to a remote machinewget
to make HTTP requestsUse them to check the network setup:
# run a DNS lookup for the API service name:
docker exec networking_rng-web_1 nslookup rng-api
# ping the IP address of the API container :
docker exec networking_rng-web_1 ping -c3 <rng-api-ip>
# try calling the API inside the web container:
docker exec networking_rng-web_1 wget -qO- rng-api/rng
The Docker DNS server returns the IP address for container names and network aliases. The web container can access the API container on the standard HTTP port 80.
Containers connected to the same network can access each other by IP address or name. There's no restriction to the traffic - any ports can be used between containers, they don't need to be published.
Traffic from outside the container network is restricted, and the only way to send traffic into a container is by publishing ports. Docker listens on the published port and sends the traffic into the target container port.
Try running a simple web server, publishing to a specific port:
docker run -d -p 8080:80 sixeyed/whoami:21.04
curl localhost:8080
The -p
flag - lowercase p - publishes a port, so here Docker listens on port 8080 and sends traffic into the container on port 80.
Ports are single-use resources, only one process can listen on them. If you repeat the command you'll get a port is already allocated
failure message.
📋 Run some more containers from the same image, using different ports (8081
, 8082
and 8083
should be free).
docker run -d -p 8081:80 sixeyed/whoami:21.04
docker run -d -p 8082:80 sixeyed/whoami:21.04
docker run -d -p 8083:80 sixeyed/whoami:21.04
Port mapping lets you listen on any free port on your machine, even though the containers are all listening on port 80 internally. Check the running containers:
docker ps
They each publish to different ports, so you can see the response from each container:
curl localhost:8081
curl localhost:8082
curl localhost:8083
Docker sets the IP address, DNS server and other network options for a container - and you can configure those too.
There's a script in this folder which we can mount in a container to print the network settings - network-info.sh. Save the path to the script in a variable to make it easier to mount in a container:
# on macOS/Linux:
scriptsPath="${PWD}/labs/networking/scripts"
chmod +x "${PWD}/labs/networking/scripts/network-info.sh"
# OR with PowerShell:
$scriptsPath="${PWD}/labs/networking/scripts"
Now run a basic Alpine container to execute the script:
docker run -v ${scriptsPath}:/scripts alpine sh /scripts/network-info.sh
These network settings are all built by Docker
📋 Repeat the run command for a new container with extra settings to specify a DNS server of 1.1.1.1
and hostname of alpine1
.
docker run --dns 1.1.1.1 --hostname alpine1 -v ${scriptsPath}:/scripts alpine sh /scripts/network-info.sh
This is useful for apps which need a particular configuration.
You can also set an IP address - but first you need to create a network with a specific IP address range:
docker network create --subnet=10.10.0.0/16 courselabs
docker run --network courselabs --ip 10.10.0.100 -v ${scriptsPath}:/scripts alpine sh /scripts/network-info.sh
Unless you configure them, IP addresses are set at random by Docker - you'll need to use a custom subnet range if Docker's IP address collides with your network or VPN.
You can configure custom networking in Docker Compose too:
📋 Run the app with Compose and test it. The API container also mounts the network test script - run that to check the IP address.
# update the app:
docker-compose -f labs/networking/compose-network.yml up -d
# try it out at http://localhost:8090
# print the container's network details:
docker exec networking_rng-api_1 sh /scripts/network-info.sh
Check the logs of the web container and you'll see it's using the custom IP address for the API container:
docker logs networking_rng-web_1
You don't often need to customize networking like this - containers are supposed to run in a dynamic environment. But this is very useful for moving older apps to containers, if they have fixed expectations about the environment.
Docker's DNS server returns the IP address for a container, using the name as the domain name.
When you run apps with Compose it builds container names like rng_rng-api_1
- so how do containers discover each other using the Compose service name?
Scale up the random number API to run multiple containers and check that the website load-balances requests across them all; then look at the containers' network setup to see how it works.
Cleanup by removing all containers:
docker rm -f $(docker ps -aq)