In this lab, we will install Docker and use it to run containers.
Chapter Details | |
---|---|
Chapter Goal | Install Docker and use it to run containers |
Chapter Sections |
Step 1 To simplify the installation process, we have provided a set convenience scripts for installing Docker. Take a look at the scripts in the install directory:
$ ls ~/k8s-examples/install/
Later on, we will utilize this directory to install Kubernetes components as well.
Step 2 Run the following script to install Docker, the script will also print the version of Docker installed:
$ sudo ~/k8s-examples/install/install-docker.sh
Step 3 Let’s verify that Docker is installed correctly. The following command downloads a test image and runs it in a container. When the container runs, it prints an informational message and exits:
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
...
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com
For more examples and ideas, visit:
https://docs.docker.com/get-started/
Step 4 By default, the docker daemon binds to a Unix socket, which is owned by the user root and other users can access it with sudo. To make it possible for regular users to use the docker client without sudo there is a group called docker. When the docker daemon starts, it makes the ownership of the socket read/writable by the docker group.
Let’s add the current user stack to the docker group:
$ sudo usermod -aG docker stack
Step 5 Usually, to make the changes to take affect, you need to logout and login again, but in our case, we will create a new login session:
$ sudo su - stack
Step 6 Check that the user stack is in the docker group:
$ id
uid=1000(stack) gid=1000(stack) groups=1000(stack),109(docker)
Step 7 Now you can use docker without sudo:
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
Step 1 Run docker ps to show running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Step 2 The output shows that there are no running containers at the moment. Use the command docker ps --help to list all options available for ps subcommand:
$ docker ps --help
Usage: docker ps [OPTIONS]
List containers
Options:
-a, --all Show all containers (default shows just running)
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print containers using a Go template
--help Print usage
-n, --last int Show n last created containers (includes all states) (default -1)
-l, --latest Show the latest created container (includes all states)
--no-trunc Don't truncate output
-q, --quiet Only display numeric IDs
-s, --size Display total file sizes
Step 3 The output shows that there are no running containers at the moment. Use the command docker ps -a to list all containers:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6e6db2a24a8e hello-world "/hello" 15 minutes ago Exited (0) 15 minutes ago dreamy_nobel
77609b91727a hello-world "/hello" 10 minutes ago Exited (0) 10 minutes ago grave_pike
In the previous section we started two containers and the command docker ps -a shows that both of them exited. Note that Docker has generated random names for the containers (the last column). In your case, these names can be different.
Step 4 Let’s remove the stopped containers which permanently discards the Read/Write image layer and the container (note: your container names will be different):
$ docker rm dreamy_nobel grave_pike
dreamy_nobel
grave_pike
Step 1 Use the command docker image --help to list all options available for image subcommand:
$ docker image --help
Usage: docker image COMMAND
Manage images
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
prune Remove unused images
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rm Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
Step 2 Let’s use the pull subcommand to download the existing Ubuntu image to our local server:
$ docker image pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5667fdb72017: Pull complete
d83811f270d5: Pull complete
ee671aafb583: Pull complete
7fc152dfb3a6: Pull complete
Digest: sha256:b88f8848e9a1a4e4558ba7cfc4acc5879e1d0e7ac06401409062ad2627e6fb58
Status: Downloaded newer image for ubuntu:latest
As you see, Docker downloaded the image ubuntu:latest because it was not on the local machine.
Step 3 Let’s run the command docker image ls to show all the images on your local host:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 42118e3df429 11 days ago 124.8 MB
hello-world latest c54a2cc56cbb 4 weeks ago 1.848 kB
Step 4 Let’s run our own “hello world” container using the downloaded Ubuntu image. Docker will use the local copy of the image:
$ docker run ubuntu /bin/echo 'Hello world'
Hello world
Step 1 As you see, Docker has downloaded the ubuntu:latest image. You can see Ubuntu version by running the following command:
$ docker run ubuntu /bin/cat /etc/issue.net
Ubuntu 18.04 LTS
Step 2 Let’s say you need an older Ubuntu release. In this case, you can specify the version you need. Docker detects that the image does not exist locally and automatically downloads it if available:
$ docker run ubuntu:14.04 /bin/cat /etc/issue.net
Unable to find image 'ubuntu:14.04' locally
14.04: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:14.04
Ubuntu 14.04.5 LTS
Step 3 The docker image ls command should show that we have two Ubuntu images downloaded locally:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 42118e3df429 11 days ago 124.8 MB
ubuntu 14.04 0ccb13bf1954 11 days ago 188 MB
hello-world latest c54a2cc56cbb 4 weeks ago 1.848 kB
Step 1 Let’s use the ubuntu image to run an interactive bash session. We will use the -t command line argument to assigns a pseudo-tty or terminal inside the new container, and the -i to make an interactive connection by grabbing the standard input of the container. Use the exit command or press Ctrl-D to exit the interactive bash session:
$ docker run -t -i ubuntu /bin/bash
root@17d8bdeda98e:/# uname -a
Linux 69467ad115bf 4.15.0-1032-aws #34-Ubuntu ...
root@17d8bdeda98e:/# exit
Step 2 Let’s check that when the shell process has finished, the container stops:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Step 1 To run a container in a background use the -d command line argument:
$ docker run -d ubuntu /bin/sh -c "while true; do date; echo hello world; sleep 1; done"
ac231579e57faf1afcb76e4f7ff22415d39af73eeeb4476eab3ea6bb8991f556
The command returned the container ID (it can be different in your case).
Step 2 Let’s use the docker ps command to see running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ac231579e57f ubuntu "/bin/sh -c 'while tr" 1 minute ago Up 11 minute evil_golick
ac231579e57f is the shortened container ID (it can be different in your case).
Step 3 Let’s use this short container ID to show the container standard output:
$ docker logs <short-id>
Thu Jan 26 00:23:45 UTC 2019
hello world
Thu Jan 26 00:23:46 UTC 2019
hello world
Thu Jan 26 00:23:47 UTC 2019
hello world
...
As we see, in the docker ps command output, the auto generated container name is evil_golick (your container can have a different name).
Step 4 Use your container name to to show the container standard output. Add options to follow the output:
$ docker logs -f <name>
Thu Jan 26 00:23:51 UTC 2019
hello world
Thu Jan 26 00:23:52 UTC 2019
hello world
Thu Jan 26 00:23:53 UTC 2019
hello world
...
^C
Use ^C to exit the command.
Step 5 Finally, let’s stop our container, adding the option to wait zero seconds before terminating the container:
$ docker stop -t 0 <name>
<name>
Step 6 Check, that there are no running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Step 1 Let’s run a simple web application. We will use the existing image training/webapp, which contains a Python Flask application. We also want to specify the container name (--name argument):
$ docker run -d -P --name hello-world-app training/webapp python app.py
...
Status: Downloaded newer image for training/webapp:latest
6e88f42d3d853762edcbfe1fe73fdc5c48865275bc6df759b83b0939d5bd2456
In the command above we specified the main process (python app.py), the -d command line argument, which tells Docker to run the container in the background. The -P command line argument tells Docker to map any required network ports inside our container to our host. This allows us to access the web application in the container.
Step 2 Use the docker ps command to list running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6e88f42d3d85 training/webapp "python app.py" 3 minutes ago Up 3 minutes 0.0.0.0:32768->5000/tcp hello-world-app
The PORTS column contains the mapped ports. In our case, Docker has exposed port 5000 (the default Python Flask port) on port 32768 (can be different in your case).
Step 3 The docker port command shows the exposed port. We will use the container name (hello-world-app in the example above, it can be different in your case):
$ docker port hello-world-app 5000
0.0.0.0:32768
Step 4 Let’s check that we can access the web application exposed port:
$ curl http://localhost:<port>/
Hello world!
Step 5 Docker achieves this by adding a Destination NAT (DNAT) rule to the host iptables rules. You can see the rule:
$ sudo iptables -t nat -L -n | grep DNAT
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:32768 to:172.17.0.2:5000
Step 6 Let’s stop our web application for now:
$ docker stop -t 0 hello-world-app
Step 7 We want to manually specify the local port to expose (-p argument). Let’s use the standard HTTP port 80:
$ docker run -d -p 80:5000 --name webapp training/webapp python app.py
249476631f7d445c6a61a6067c4c24461b0e51ea0667c86cb1bce301981ecd22
Step 8 Let’s check that the port 80 is exposed:
$ sudo iptables -t nat -L -n | grep DNAT
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:5000
Step 9 Access your application from a browser using your host public ip address:
$ echo $PublicIP
<public ip address of your host>
Copy and paste to your web-browser http://<public ip address of your host>
Step 1 You can use the docker inspect command to see the configuration and status information for the specified container in json output format:
$ docker inspect webapp
[
{
"Id": "249476631f7d...",
"Created": "2019-06-11T23:42:56.932135327Z",
"Path": "python",
"Args": [
"app.py"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 16055,
"ExitCode": 0,
"Error": "",
...
Step 2 You can specify a filter (-f command line argument) to show only specific elements values. For example:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' webapp
172.17.0.2
This command returned the IP address of the container.
Step 3 To filter and display composite subsets in json format use the json verb. Use jq to pretty-print the json output:
$ docker inspect -f '{{json .NetworkSettings.Networks}}' webapp | jq
{
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "8b6af9d3a573b7e3e1ac63d6171cb76575fc9c744c4ee1985e68b7a58adccc2b",
"EndpointID": "56348f7c1e14b55b9ebf4c24660827282c34bf8eb9cdfaea12c67d1c84dc5575",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
Step 1 Use the docker exec command to execute a command in the running container. For example:
$ docker exec webapp ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.2 0.0 52320 17384 ? Ss 00:11 0:00 python app.py
root 26 0.0 0.0 15572 2104 ? Rs 00:12 0:00 ps aux
The same command with the -it command line argument can be used to run an interactive session in the container:
$ docker exec -it webapp bash
root@249476631f7d:/opt/webapp# ps auxw
ps auxw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 52320 17384 ? Ss 00:11 0:00 python app.py
root 32 0.0 0.0 18144 3064 ? Ss 00:14 0:00 bash
root 47 0.0 0.0 15572 2076 ? R+ 00:16 0:00 ps auxw
Step 2 Use the exit command or press Ctrl-D to exit the interactive bash session:
root@249476631f7d:/opt/webapp# exit
Step 1 Let’s stop the container with web application:
$ docker stop webapp
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.
Step 2 You can start the container later using the docker start command:
$ docker start webapp
Step 3 Check that the web application works:
$ curl http://localhost/
Hello world!
The docker cp command allows you to copy files from the container to the local machine or from the local file system to the container. This command works for a running or stopped container.
Step 1 Let’s copy the container’s app.py file to the local machine:
$ docker cp webapp:/opt/webapp/app.py .
Step 2 Edit the local app.py file. For example, change the line return 'Hello '+provider+'!' to return 'Hello '+provider+'!!!\n'. Copy the modified file back and restart the container:
$ docker cp app.py webapp:/opt/webapp/
Step 3 You also can restart the running container using the docker restart command. The -t command line argument specifies a number of seconds to wait for stop before killing the container, overriding the default value of 10:
$ docker restart -t 0 webapp
Step 4 Check that the modified web application works::
$ curl http://localhost/
Hello world!!!
Step 1 By default, Docker runs containers in the legacy bridge network, which does not support service discovery. However, a container can be manually linked to other containers to enable discovery by name. Start a database container and link it to the webapp container:
$ docker run -d --name db --link webapp training/postgres
Step 2 Check that the webapp and db containers are running:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
...
c3afff20019a training/postgres "su postgres -c '/usr" 4 minutes ago Up 4 minutes 5432/tcp db
62ed4a627356 training/webapp "python app.py" 30 minutes ago Up 30 minutes 0.0.0.0:80->5000/tcp webapp
Step 3 The docker network ls command shows Docker networks:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
d428e49e4869 bridge bridge local
0d1f78528cc5 host host local
4a07cef84617 none null local
Step 4 You can inspect your containers to see the networks they are connected to:
$ docker inspect -f '{{json .NetworkSettings.Networks}}' webapp | jq
{
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "8b6af9d3a573b7e3e1ac63d6171cb76575fc9c744c4ee1985e68b7a58adccc2b",
"EndpointID": "ddd88e3ac5164e02b716231c2b29a930d6bedcf355e3002e9aec359d26e40861",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
$ docker inspect -f '{{json .NetworkSettings.Networks}}' db | jq
{
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "8b6af9d3a573b7e3e1ac63d6171cb76575fc9c744c4ee1985e68b7a58adccc2b",
"EndpointID": "ce842a346f4c682fb95aec42b2f2bbea157c6466c92240c68b9cd5cdaf1f60ec",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
}
}
Step 5 Get IP addresses of the webapp and db containers by inspecting the legacy bridge network containers:
$ docker network inspect -f '{{json .Containers}}' bridge | jq
{
"0b423ca8e4a6920493b62f8227f75b1bde9e7183ce36e1b9232594fa01ae1701": {
"Name": "webapp",
"EndpointID": "56348f7c1e14b55b9ebf4c24660827282c34bf8eb9cdfaea12c67d1c84dc5575",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"49dfb55e294793fa6a7b6737d9f1771637fb2947c76b1d9b277f5f84301b4fc8": {
"Name": "db",
"EndpointID": "c5dfc42497d8fc09676e58e9e1611daf77c6ae7758a98edb2e5fc5551121d075",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
}
Step 6 In your case, the IP addresses can be different. Let’s ping webapp from the db container using the container name:
$ docker exec db ping -c 1 webapp
PING webapp (172.17.0.2) 56(84) bytes of data.
64 bytes from webapp (172.17.0.2): icmp_seq=1 ttl=64 time=0.055 ms
--- webapp ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.055/0.055/0.055/0.000 ms
This is possible becase we --link webapp when we started the db container in step 1. Check how Docker makes discovery by name work:
$ docker exec db cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 webapp 0b423ca8e4a6
172.17.0.3 cf2e5d7595c8
Note that you can ping the db container from the webapp continer by IP address only. You can check what other containers a container is linked with using:
$ docker inspect -f "{{ .HostConfig.Links }}" db
[/webapp:/db/webapp]
In addition to modifying the /etc/hosts file Docker also injects several environment variables for each container that is being linked to:
$ docker exec db env | grep WEBAPP
WEBAPP_PORT=tcp://172.17.0.2:5000
WEBAPP_PORT_5000_TCP=tcp://172.17.0.2:5000
WEBAPP_PORT_5000_TCP_ADDR=172.17.0.2
WEBAPP_PORT_5000_TCP_PORT=5000
WEBAPP_PORT_5000_TCP_PROTO=tcp
WEBAPP_NAME=/db/webapp
Step 7 Docker relies on Linux Bridge virtual switch devices to implement container connectivity:
You can show the bridge status using the Linux Bridge CLI brctl:
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242539faa49 no veth5c072f5
vethaf6bbca
Trace the two ends of one of the veth-pairs to see how the container is connected to the bridge:
$ ip a show dev veth5c072f5
21: veth5c072f5@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 82:cc:d2:66:33:7f brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::80cc:d2ff:fe66:337f/64 scope link
valid_lft forever preferred_lft forever
$ docker exec webapp ip a show dev eth0
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
You can see that interface 21 on the host and interface 20 in the webapp container are the two ends of the same veth-pair.
Step 1 By default, Docker runs containers in the bridge network. You may want to isolate one or more containers in a separate network. Let’s create a new network:
$ docker network create my-network -d bridge --subnet 172.19.0.0/16
The -d bridge command line argument specifies the bridge network driver and the --subnet command line argument specifies the network segment in CIDR format. If you do not specify a subnet when creating a network, then Docker assigns a subnet automatically, so it is a good idea to specify a subnet to avoid potential conflicts with any existing networks.
Step 2 To check that the new network is created, execute docker network ls:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
d428e49e4869 bridge bridge local
0d1f78528cc5 host host local
56ef0481820d my-network bridge local
4a07cef84617 none null local
Step 3 Let’s inspect the new network:
$ docker network inspect my-network
[
{
"Name": "my-network",
"Id": "56ef0481820d...",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.19.0.0/16"
}
]
},
"Internal": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
Step 4 As expected, there are no containers connected to the my-network. Let’s recreate the db container in the my-network:
$ docker rm -f db
$ docker run -d --network=my-network --name db training/postgres
Step 5 Inspect the containers in my-network:
$ docker network inspect -f '{{json .Containers}}' my-network | jq
{
"7aa573cf38e8d222965631a3fa91932e4f29373de50b42266c3aff701522cfc2": {
"Name": "db",
"EndpointID": "ce549fc3b95427997c9b14f91a09665e14c6a36d704e0a68ce015161473ecc96",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
}
}
As you see, the db container is connected to the my-network and has 172.19.0.2 address.
Let’s confirm that a bridge network is just another Linux Bridge virtual switch device:
$ brctl show
bridge name bridge id STP enabled interfaces
br-ba62f7084206 8000.024237df3c6f no veth3582635
docker0 8000.0242539faa49 no veth5c072f5
Step 6 Let’s ping the IP address of the webapp again:
$ docker exec -it db bash
root@c3afff20019a:/# ping -c 1 172.17.0.2
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
--- 172.17.0.3 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
As expected, the webapp container is no longer accessible from the db container, because they are connected to different networks.
Step 7 Let’s connect the webapp container to the my-network:
$ docker network connect my-network webapp
Check that the webapp container now is connected to the my-network:
$ docker network inspect -f '{{json .Containers}}' my-network | jq
{
"0b423ca8e4a6920493b62f8227f75b1bde9e7183ce36e1b9232594fa01ae1701": {
"Name": "webapp",
"EndpointID": "a624841cc2dd4ab98b743039ba9355f63c5ab2bdd0b88412eb7f79fa58265e1f",
"MacAddress": "02:42:ac:13:00:03",
"IPv4Address": "172.19.0.3/16",
"IPv6Address": ""
},
"7aa573cf38e8d222965631a3fa91932e4f29373de50b42266c3aff701522cfc2": {
"Name": "db",
"EndpointID": "ce549fc3b95427997c9b14f91a09665e14c6a36d704e0a68ce015161473ecc96",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
}
}
The output shows that two containers are connected to the my-network and the webapp container has 172.19.0.3 address in that network.
Step 8 Check that the webapp container is accessible from the db container using its new IP address:
$ docker exec -it db ping -c 1 webapp
PING webapp (172.19.0.3) 56(84) bytes of data.
64 bytes from webapp.my-network (172.19.0.3): icmp_seq=1 ttl=64 time=0.073 ms
--- webapp ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.073/0.073/0.073/0.000 ms
Note that we did not use --link webapp yet we can ping by container name. That’s because user defined network implement service discovery using Docker’s built-in DNS server. Check that webapp can also ping db by name:
$ docker exec -it webapp ping -c 1 db
PING db (172.19.0.2) 56(84) bytes of data.
64 bytes from db.my-network (172.19.0.2): icmp_seq=1 ttl=64 time=0.047 ms
--- db ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.047/0.047/0.047/0.000 ms
Step 9 Note that the webapp container is now connected to multiple networks:
$ docker inspect -f '{{json .NetworkSettings.Networks}}' webapp | jq
{
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "8b6af9d3a573b7e3e1ac63d6171cb76575fc9c744c4ee1985e68b7a58adccc2b",
"EndpointID": "56348f7c1e14b55b9ebf4c24660827282c34bf8eb9cdfaea12c67d1c84dc5575",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
},
"my-network": {
"IPAMConfig": {},
"Links": null,
"Aliases": [
"0b423ca8e4a6"
],
"NetworkID": "ba62f708420615f6d5e91d01d07b9021ed71fd20299134d843e589bdfead3b0b",
"EndpointID": "a624841cc2dd4ab98b743039ba9355f63c5ab2bdd0b88412eb7f79fa58265e1f",
"Gateway": "172.19.0.1",
"IPAddress": "172.19.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:13:00:03",
"DriverOpts": null
}
}
And has multiple ip addresses:
$ docker exec webapp ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
29: eth1@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.3/16 brd 172.19.255.255 scope global eth1
valid_lft forever preferred_lft forever
Step 10 You can remove the existing containers. You should stop the container before removing it. Alternatively you can use the -f command line argument:
$ docker rm -f webapp
$ docker rm -f db
Step 1 Add a data volume to a container:
$ docker run -d -P --name webapp -v /webapp training/webapp python app.py
This command started a new container and created a new volume inside the container at /webapp.
Step 2 Locate the volume on the host using the docker inspect command:
$ docker inspect -f '{{json .Mounts}}' webapp | jq
[
{
"Type": "volume",
"Name": "dce199cf635cc7be49b3e5444f2286687a460849bcceb05a65103e0abe10d53d",
"Source": "/var/lib/docker/volumes/dce199cf635cc7be49b3e5444f2286687a460849bcceb05a65103e0abe10d53d/_data",
"Destination": "/webapp",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
Alternatively, you can specify a host directory you want to use as a data volume:
$ mkdir db
$ docker run -d --name db -v /home/stack/db:/db training/postgres
Step 3 Create a new file in the /db directory of the db container:
$ docker exec db touch /db/hello_from_db_container
Step 4 Check that the local db directory contains the new file:
$ ls -l db
total 0
-rw-r--r-- 1 root root 0 Nov 16 00:38 hello_from_db_container
Step 5 Check that the data volume is persistent. Remove the db container:
$ docker rm -f db
Step 6 Create the db container again:
$ docker run -d --name db -v /home/stack/db:/db training/postgres
Step 7 Check that its /db directory contains the hello_from_db_container file:
$ docker exec db ls /db
hello_from_db_container
Step 1 Let’s create a new named container with a volume to share:
$ docker create -v /dbdata -v /appconfig --name dbstore training/postgres /bin/true
This container does not run an application and reuses the training/postgres image.
Step 2 Use the --volumes-from flag to mount the /dbdata volume in another containers:
$ docker run -d --volumes-from dbstore --name db1 training/postgres
$ docker run -d --volumes-from dbstore --name db2 training/postgres
Step 3 Check all three containers to verify the mounted directories are the same:
$ docker inspect dbstore db1 db2 | \
jq '.[] | {Name: .Name, Volumes: [.Mounts[] | {Source: .Source, Destination: .Destination}]}'
Step 4 Create some content on db1 container mountpoint:
$ docker exec db1 touch /dbdata/hello_from_db1
Step 5 Observe the content on db2 container mountpoint:
$ docker exec db2 ls /dbdata
A common software design pattern is to utilize one container to write data to a volume and for another closely-working container to retrieve and process the data from the same shared volume.
Step 6 Clean up by removing all running and terminated containers before we move to the next lab:
$ docker rm -f $(docker ps -qa)
Step 7 Remove the extra network you created:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
6de50d42f1a4 bridge bridge local
9936d152eb18 host host local
324ebba7db04 my-network bridge local
02a5328082f2 none null local
$ docker network rm my-network
my-network
Checkpoint