Docker (2/2): Docker Compose and advanced topics

Docker (2/2): Docker Compose and advanced topics

In the first part we learned how to manage containers, data and volumes, images, how to create applications with Docker, and how to use GPUs. In this second chapter we take the leap to **Docker Compose** to orchestrate multiple containers and to a series of **advanced topics** in Docker. 🐳

Disclaimer: This post has been translated to English using a machine translation model. Please, let me know if you find any mistakes.

📚 **This entry is part of the _Docker Guide_ series**, divided into two chapters that are read in order:

> * Part 1: Containers, images and applications

* 👉 **Part 2: Docker Compose and advanced topics**

Docker composelink image 31

Docker Compose vs docker-composelink image 32

docker-compose was a tool created to help with the maintenance of images and containers, and it had to be installed separately from Docker. However, Docker included it in its latest versions, and it is no longer necessary to install it; however, to use it, instead of using the docker-compose command, you must use the docker compose command. On many sites you will find information with docker-compose, but when you install Docker, docker compose will already be installed, so everything that could be done with docker-compose is compatible with docker compose

Docker composelink image 33

Docker Compose is a Docker tool that does everything we have seen so far, but saving us time and effort. By editing a .yml file, we can tell Docker Compose to create all the containers we want.

To use it once, there won’t be much difference between writing all the commands we saw before or writing the .yml file, but when you want to have the same container configuration working again, simply calling the .yml file will recreate the entire configuration.

Let's create a folder where we will store the Docker Compose files

	
< > Input
Python
!mkdir dockerComposeFiles
Copied

We create the .yml file inside

	
< > Input
Python
!touch dockerComposeFiles/docker-compose.yml
Copied

A Docker Compose file has to start with the version

version: "<v.v>"

At the time of writing this, the latest version is 3.8, so we write that

*docker-compose.yml*:

version: "3.8"

The services, which are the containers, are listed below. For each service, you must specify the image and, in addition, you can add other parameters such as ports, environment variables, etc.

services:
container1:
image: ubuntu

container2:
image: ubuntu

The docker-compose.yml would look like this:

version: "3.8"

services:
container1:
image: ubuntu
container2:
image: ubuntu

Once we have created the file, in its path, we can run everything using the docker compose up command, but in addition, by adding the -d option, we will make it run in the background

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠿ Network dockercomposefiles_default Created 0.1s
⠋ Container dockercomposefiles-container2-1 Creating 0.0s
⠋ Container dockercomposefiles-container1-1 Creating 0.0s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠙ Container dockercomposefiles-container2-1 Creating 0.1s
⠙ Container dockercomposefiles-container1-1 Creating 0.1s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.2s
⠿ Container dockercomposefiles-container1-1 Starting 0.2s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.3s
⠿ Container dockercomposefiles-container1-1 Starting 0.3s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.4s
⠿ Container dockercomposefiles-container1-1 Starting 0.4s
[+] Running 1/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Starting 0.5s
⠿ Container dockercomposefiles-container1-1 Starting 0.5s
[+] Running 2/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Started 0.5s
⠿ Container dockercomposefiles-container1-1 Starting 0.6s
[+] Running 3/3
⠿ Network dockercomposefiles_default Created 0.1s
⠿ Container dockercomposefiles-container2-1 Started 0.5s
⠿ Container dockercomposefiles-container1-1 Started 0.7s

If we look closely, it has created two containers dockercomposefiles-container1-1 and dockercomposefiles-container2-1, and the network that connects them dockercomposefiles_default

Let's delete the two containers

	
< > Input
Python
!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1
Copied
>_ Output
			
dockercomposefiles-container1-1
dockercomposefiles-container2-1

And we delete the network that has been created

	
< > Input
Python
!docker network rm dockercomposefiles_default
Copied
>_ Output
			
dockercomposefiles_default

Let's try to do what we did before with what we know so far. We create a new image that comes with ping installed

*Dockerfile*:

FROM ubuntu:20.04
      RUN apt update
      RUN apt install iputils-ping -y

And we compile it

	
< > Input
Python
!docker build -t ubuntu:ping ./dockerImages
Copied
>_ Output
			
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:20.04
---&gt; a0ce5a295b63
Step 2/3 : RUN apt update
---&gt; Running in 3bd5278d39b4
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
Get:2 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
Get:3 http://security.ubuntu.com/ubuntu focal-security/universe amd64 Packages [898 kB]
Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]
Get:5 http://archive.ubuntu.com/ubuntu focal-backports InRelease [108 kB]
Get:6 http://archive.ubuntu.com/ubuntu focal/universe amd64 Packages [11.3 MB]
Get:7 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [2133 kB]
Get:8 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 Packages [27.5 kB]
Get:9 http://security.ubuntu.com/ubuntu focal-security/restricted amd64 Packages [1501 kB]
Get:10 http://archive.ubuntu.com/ubuntu focal/main amd64 Packages [1275 kB]
Get:11 http://archive.ubuntu.com/ubuntu focal/restricted amd64 Packages [33.4 kB]
Get:12 http://archive.ubuntu.com/ubuntu focal/multiverse amd64 Packages [177 kB]
Get:13 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [2594 kB]
Get:14 http://archive.ubuntu.com/ubuntu focal-updates/restricted amd64 Packages [1613 kB]
Get:15 http://archive.ubuntu.com/ubuntu focal-updates/multiverse amd64 Packages [30.2 kB]
Get:16 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages [1200 kB]
Get:17 http://archive.ubuntu.com/ubuntu focal-backports/universe amd64 Packages [27.4 kB]
...
Successfully built c3d32aa9de02
Successfully tagged ubuntu:ping
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

We verify that it has been created

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu test a78cf3ea16d8 25 hours ago 77.8MB
nginx latest 2d389e545974 33 hours ago 142MB
ubuntu latest 2dc39ba059dc 12 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 12 days ago 72.8MB
hello-world latest feb5d9fea6a5 11 months ago 13.3kB

We changed the tag

	
< > Input
Python
!docker tag ubuntu:ping maximofn/ubuntu:ping
Copied
	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu ping c3d32aa9de02 About a minute ago 112MB
maximofn/ubuntu test c3d32aa9de02 About a minute ago 112MB
nginx latest 2d389e545974 33 hours ago 142MB
ubuntu latest 2dc39ba059dc 12 days ago 77.8MB
ubuntu 20.04 a0ce5a295b63 12 days ago 72.8MB
hello-world latest feb5d9fea6a5 11 months ago 13.3kB

We edit the Docker Compose file so that it pulls the images we just created

*docker-compose.yml*:

version: "3.8"

services:
container1:

markdown

image: maximofn/ubuntu:ping

container2:
image: maximofn/ubuntu:ping

And we also tell it to execute a no-op

The docker-compose.yml would look like this:

version: "3.8"

services:
container1:
image: ubuntu
command: tail -f /dev/null

container2:
image: ubuntu
command: tail -f /dev/null

We set it up

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
...
⠸ Container dockercomposefiles-container2-1 Recreate 1.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Recreated 0.1s
...
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 10.8s
⠿ Container dockercomposefiles-container2-1 Started 10.9s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 10.8s
⠿ Container dockercomposefiles-container2-1 Started 10.9s

We see the containers that are running

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
935939e5a75d maximofn/ubuntu:ping "tail -f /dev/null" 15 seconds ago Up 13 seconds dockercomposefiles-container2-1
f9138d7064dd maximofn/ubuntu:ping "tail -f /dev/null" 25 seconds ago Up 13 seconds dockercomposefiles-container1-1

Both containers are running, now we enter one and try to ping the other

$ docker exec -it dockercomposefiles-container1-1 bash
root@f9138d7064dd:/# ping dockercomposefiles-container2-1
PING dockercomposefiles-container2-1 (172.21.0.3) 56(84) bytes of data.
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=1 ttl=64 time=0.110 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=4 ttl=64 time=0.075 ms^C
--- dockercomposefiles-container2-1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.110/0.025 ms

As we can see, we can run ping; we have successfully built the image with ping installed. Additionally, in the docker-compose we have made it execute a no-operation so that the containers keep running

We delete the two containers and the network that we have created

	
< > Input
Python
!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1
Copied
>_ Output
			
dockercomposefiles-container1-1
dockercomposefiles-container2-1
	
< > Input
Python
!docker network rm dockercomposefiles_default
Copied
>_ Output
			
dockercomposefiles_default

How Docker Compose names containerslink image 34

If we look closely, the containers created by Docker are called dockercomposefiles-container1-1 and dockercomposefiles-container2-1. This is because the folder containing the Docker Compose file is in a folder called dockerComposeFiles, which is why the first part of the container names is dockercomposefiles, followed by the name of the service we gave in the Docker Compose file (container1 and container2) and finally a number so that more can be created if necessary

Similarly, the same happens with the network name that has been created dockercomposefiles_default

Logs in docker composelink image 35

Let’s now change the Docker Compose file. In the lines where we had command: tail -f /dev/null, we will set command: ping 0.0.0.0

And we also tell it to execute a no-operation

The docker-compose.yml would look like this:

version: "3.8"

services:
container1:
image: ubuntu
command: ping 0.0.0.0

container2:
image: ubuntu

markdown

command: ping 0.0.0.0

We do this so that each container is continuously outputting the ping, thus simulating some logs

If we run the docker compose again

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container1-1 Recreate 0.2s
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container1-1 Recreate 0.3s
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container1-1 Recreate 0.4s
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container1-1 Recreate 0.5s
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container1-1 Recreate 0.6s
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container1-1 Recreate 0.7s
⠦ Container dockercomposefiles-container2-1 Recreate 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container1-1 Recreate 0.8s
⠧ Container dockercomposefiles-container2-1 Recreate 0.8s
[+] Running 0/2
...
⠿ Container dockercomposefiles-container1-1 Starting 11.0s
⠿ Container dockercomposefiles-container2-1 Started 11.0s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 11.1s
⠿ Container dockercomposefiles-container2-1 Started 11.0s

Now we can view the logs of both containers using the docker compose logs command

	
< > Input
Python
!cd dockerComposeFiles && docker compose logs
Copied
>_ Output
			
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 ms
dockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
...
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=214 ttl=64 time=0.015 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=215 ttl=64 time=0.021 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=216 ttl=64 time=0.020 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=217 ttl=64 time=0.049 ms

As we can see, we can view the logs of both containers, but if we want to see only those of one, we can specify the **service name**

	
< > Input
Python
!cd dockerComposeFiles && docker compose logs container1
Copied
>_ Output
			
dockercomposefiles-container1-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.023 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.031 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.022 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.032 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.029 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.031 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.024 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.029 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.032 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.034 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.028 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.034 ms
...
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=332 ttl=64 time=0.027 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=333 ttl=64 time=0.030 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=334 ttl=64 time=0.033 ms
dockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=335 ttl=64 time=0.036 ms
	
< > Input
Python
!cd dockerComposeFiles && docker compose logs container2
Copied
>_ Output
			
dockercomposefiles-container2-1 | PING 0.0.0.0 (127.0.0.1) 56(84) bytes of data.
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=23 ttl=64 time=0.035 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=24 ttl=64 time=0.037 ms
...
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=340 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=341 ttl=64 time=0.033 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=342 ttl=64 time=0.034 ms
dockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=343 ttl=64 time=0.036 ms

If we want to view the logs continuously, we can add the -f option: docker compose logs -f <service name>

If I have created a Docker Compose with more than two services, when I want to view the logs of several services, I only need to add more names to the command, docker compose logs <name service 1> <name service 2> ...

Exec serviceslink image 36

As we have seen, with the exec command we can enter a container by specifying the container name, the command to be executed, and the -it option. With Docker Compose this is easier, since only the service name and the command are needed, but the -it option is not necessary because Docker Compose assumes it by default.

$ docker compose exec container1 bash
root@a7cf282fe66c:/#

Stopping docker composelink image 37

When we're done working, with a single command (stop), Docker Compose handles everything; there's no need to stop each container one by one.

	
< > Input
Python
!cd dockerComposeFiles && docker compose stop
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container2-1 Stopping 0.1s
⠋ Container dockercomposefiles-container1-1 Stopping 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container2-1 Stopping 0.2s
⠙ Container dockercomposefiles-container1-1 Stopping 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container2-1 Stopping 0.3s
⠹ Container dockercomposefiles-container1-1 Stopping 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container2-1 Stopping 0.4s
⠸ Container dockercomposefiles-container1-1 Stopping 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container2-1 Stopping 0.5s
⠼ Container dockercomposefiles-container1-1 Stopping 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container2-1 Stopping 0.6s
⠴ Container dockercomposefiles-container1-1 Stopping 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container2-1 Stopping 0.7s
⠦ Container dockercomposefiles-container1-1 Stopping 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container2-1 Stopping 0.8s
⠧ Container dockercomposefiles-container1-1 Stopping 0.8s
...
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Stopped 10.4s
⠸ Container dockercomposefiles-container1-1 Stopping 10.4s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Stopped 10.4s
⠿ Container dockercomposefiles-container1-1 Stopped 10.4s

As you can see, docker compose has stopped both containers, but has not deleted them, nor has it deleted the network

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e6c1dd9adb2 maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container2-1
a7cf282fe66c maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container1-1
	
< > Input
Python
!docker network ls
Copied
>_ Output
			
NETWORK ID NAME DRIVER SCOPE
13cc632147f3 bridge bridge local
d4a2f718cd83 dockercomposefiles_default bridge local
da1f5f6fccc0 host host local
d3b0d93993c0 none null local

Docker Compose as a development toollink image 38

As we saw before, in order to develop, the ideal thing would be to share the folder that contains the code with the service. With Docker Compose, this is done by adding the volumes label to the Docker Compose file. First, we need to add the path to the folder where the code is on the host and then the path in the container.

*docker-compose.yml*:

version: "3.8"

services:
container1:
image: ubuntu
command: ping 0.0.0.0
volumes:
- ../dockerHostFolder/:/dockerContainerFolder

container2:
image: ubuntu
command: ping 0.0.0.0

As you can see, I have set the host folder path as relative.

If we start the Docker Compose

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠋ Container dockercomposefiles-container1-1 Recreate 0.1s
⠿ Container dockercomposefiles-container2-1 Created 0.0s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.2s
⠿ Container dockercomposefiles-container2-1 Starting 0.2s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.3s
⠿ Container dockercomposefiles-container2-1 Starting 0.3s
[+] Running 0/2
⠿ Container dockercomposefiles-container1-1 Starting 0.4s
⠿ Container dockercomposefiles-container2-1 Starting 0.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container1-1 Started 0.5s
⠿ Container dockercomposefiles-container2-1 Starting 0.5s
[+] Running 2/2
⠿ Container dockercomposefiles-container1-1 Started 0.5s
⠿ Container dockercomposefiles-container2-1 Started 0.6s

If we enter the container, we can see what is inside the text.txt file

$ docker compose exec container1 bash
root@c8aae9d619d3:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
hello container

If we now open it on the host, type hola host, and check it again in the container

root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
hello host

And now the other way around, if we modify it in the container

root@c8aae9d619d3:/# echo hello compose > dockerContainerFolder/text.txt
root@c8aae9d619d3:/# exit
exit

If we view it from the host, we should get hola compose

	
< > Input
Python
!cat dockerHostFolder/text.txt
Copied
>_ Output
			
hola compose

Port exposure in docker composelink image 39

We can also configure the ports in the Docker Compose file, using the ports label, specifying the host port and then the service IP.

ports:
- <host port>:<service port>

Docker compose in a team - docker overridelink image 40

If we are a group of people developing with Docker using Docker Compose, it is likely that many people will be modifying the Docker Compose file, which can cause it to not sync properly and lead to conflicts.

To solve this, Docker offers a tool called Docker Override. This way, there can be a base Docker Compose file and each one can modify it using Docker Override.

To do this, we now need to create a file called docker-compose.override.yml that we will be able to edit

	
< > Input
Python
!touch dockerComposeFiles/docker-compose.override.yml
Copied

If we now try to start Docker Compose, we are going to receive an error

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
Top-level object must be a mapping

And this is because Docker Compose has detected that there is a file called docker-compose.override.yml and that it is empty, so we are going to edit it. The docker-compose.override.yml file is used to edit the docker-compose.yml file, so if, for example, we want to make a change in the container2 service to add a volume, we would write the docker-compose.override.yml file like this

*docker-compose.override.yml*:

version: "3.8"

services:
container2:

markdown

volumes:

- ../dockerHostFolder/:/dockerOverrideFolder

Notice that the shared folder in the service is named dockerOverrideFolder, so we are going to bring up the Docker Compose and see if we can see that folder in the container2 container

	
< > Input
Python
!cd dockerComposeFiles && docker compose up -d
Copied
>_ Output
			
[+] Running 1/0
⠋ Container dockercomposefiles-container2-1 Recreate 0.1s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠙ Container dockercomposefiles-container2-1 Recreate 0.2s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠹ Container dockercomposefiles-container2-1 Recreate 0.3s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠸ Container dockercomposefiles-container2-1 Recreate 0.4s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠼ Container dockercomposefiles-container2-1 Recreate 0.5s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠴ Container dockercomposefiles-container2-1 Recreate 0.6s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠦ Container dockercomposefiles-container2-1 Recreate 0.7s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 1/2
⠧ Container dockercomposefiles-container2-1 Recreate 0.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
...
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Starting 10.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Started 10.8s
⠿ Container dockercomposefiles-container1-1 Running 0.0s

We see that it took 10 seconds to mount the container2 service; that's because it had been applying the changes.

$ docker compose exec container2 bash
root@d8777a4e611a:/# ls dockerOverrideFolder/
bindFile.txt fileExtract.txt text.txt
root@d8777a4e611a:/# cat dockerOverrideFolder/text.txthello compose
root@d8777a4e611a:/# exit

We bring down the Compose and delete the containers and the network created

	
< > Input
Python
!cd dockerComposeFiles && docker compose down
Copied
>_ Output
			
[+] Running 0/0
⠋ Container dockercomposefiles-container2-1 Stopping 0.1s
⠋ Container dockercomposefiles-container1-1 Stopping 0.1s
[+] Running 0/2
⠙ Container dockercomposefiles-container2-1 Stopping 0.2s
⠙ Container dockercomposefiles-container1-1 Stopping 0.2s
[+] Running 0/2
⠹ Container dockercomposefiles-container2-1 Stopping 0.3s
⠹ Container dockercomposefiles-container1-1 Stopping 0.3s
[+] Running 0/2
⠸ Container dockercomposefiles-container2-1 Stopping 0.4s
⠸ Container dockercomposefiles-container1-1 Stopping 0.4s
[+] Running 0/2
⠼ Container dockercomposefiles-container2-1 Stopping 0.5s
⠼ Container dockercomposefiles-container1-1 Stopping 0.5s
[+] Running 0/2
⠴ Container dockercomposefiles-container2-1 Stopping 0.6s
⠴ Container dockercomposefiles-container1-1 Stopping 0.6s
[+] Running 0/2
⠦ Container dockercomposefiles-container2-1 Stopping 0.7s
⠦ Container dockercomposefiles-container1-1 Stopping 0.7s
[+] Running 0/2
⠧ Container dockercomposefiles-container2-1 Stopping 0.8s
⠧ Container dockercomposefiles-container1-1 Stopping 0.8s
...
⠸ Container dockercomposefiles-container2-1 Stopping 10.4s
⠸ Container dockercomposefiles-container1-1 Stopping 10.4s
[+] Running 1/2
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removing 10.5s
[+] Running 2/2
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removed 10.5s
⠋ Network dockercomposefiles_default Removing 0.1s
[+] Running 3/3
⠿ Container dockercomposefiles-container2-1 Removed 10.4s
⠿ Container dockercomposefiles-container1-1 Removed 10.5s
⠿ Network dockercomposefiles_default Removed 0.2s

In this case, only with down Docker Compose has stopped and deleted everything, since, as we can see in the containers and in the network, it says Removed

Docker compose restartlink image 41

When writing a Docker Compose file, we can add the restart label so that if the container crashes, it restarts automatically

restart: always

In this way, if the container crashes, it will restart automatically. If we want it to restart only a certain number of times, we can add the on-failure option

restart: on-failure:<number>

Now the container will restart a number of times, but if it fails more times, it will not restart. If we want it to always restart, we can add the unless-stopped option.

restart: unless-stopped

Now the container will always restart, unless it is stopped manually

Advanced Dockerlink image 42

Manage workspace environmentlink image 43

Deletion of stopped containerslink image 44

After spending some time developing, we may have several stopped containers saved on the computer. This ends up taking up memory, so with docker container prune we can delete all the ones that are stopped

	
< > Input
Python
!docker run ubuntu
Copied
	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
effcee24f54a ubuntu "bash" 37 seconds ago Exited (0) 36 seconds ago musing_rosalind
$ docker container prune
WARNING! Esto eliminará todos los contenedores detenidos.
Are you sure you want to continue? [y/N] y
Contenedores eliminados:
effcee24f54aab22e34fdea2465b3b7af132d8c627e5432ba3e915a370876977

Total reclaimed space: 0B

In this case, we have saved 0 bytes, but in the case of leaving containers turned off after a lot of development, the memory savings will surely be greater

Deletion of all containerslink image 45

In case there are running containers, we can remove all containers using another command

The command docker ps -q returns the ID of all containers, so with the command docker rm -f $(docker ps -aq) we will stop and delete them all

	
< > Input
Python
!docker run -d ubuntu tail -f /dev/null
Copied
>_ Output
			
c22516186ef7e3561fb1ad0d508a914857dbc61274a218f297c4d80b1fc33863
	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c22516186ef7 ubuntu "tail -f /dev/null" About a minute ago Up About a minute agitated_knuth
	
< > Input
Python
!docker rm -f $(docker ps -aq)
Copied
>_ Output
			
c22516186ef7
	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Deleting Everythinglink image 46

As we have seen, Docker also creates networks, images, volumes, etc., so with the command docker system prune we can delete all stopped containers, all networks that are not used by at least one container, duplicate images, and anything duplicated in the build cache

$ docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all dangling build cache
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

As before, not much space has been saved, but after a long time of development, the savings will be considerable

Host resource usage by containerslink image 47

For example, when creating a container, we can limit the RAM that the host can use using the --memory option

	
< > Input
Python
!docker run -d --memory 1g ubuntu tail -f /dev/null
Copied
>_ Output
			
d84888eafe531831ef8915d2270422365adec02678122bf59580e2da782e6972

But with docker ps we don't have access to the resources the container is consuming

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d84888eafe53 ubuntu "tail -f /dev/null" 35 seconds ago Up 34 seconds musing_ritchie

For that, we have the docker stats command

$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d84888eafe53 musing_ritchie 0.00% 540KiB / 1GiB 0.05% 5.62kB / 0B 0B / 0B 1

This is very useful if we want to simulate an environment with a RAM limit

Properly stopping containers: SHELL vs EXEClink image 48

As we have explained, when we assign a process to a container, when that process ends, the container stops, but sometimes we may encounter problems with this. Let's create a new folder called Dockerfile_loop

	
< > Input
Python
!mkdir Dockerfile_loop
Copied

Now we are going to create a file called loop.sh inside Dockerfile_loop

	
< > Input
Python
!touch Dockerfile_loop/loop.sh
Copied

And we are going to write the following inside loop.sh

#!/usr/bin/env bash
      trap "exit 0" SIGTERM
      while true; do :; done

If I run this script on the host, it keeps running until I press CTRL+C

./loop
^C

Now we are going to create a Dockerfile file inside Dockerfile_loop

	
< > Input
Python
!touch Dockerfile_loop/Dockerfile
Copied

*Dockerfile*:

FROM ubuntu:trusty
      COPY ["loop.sh", "/"]CMD /loop.sh

Let's create an image based on Ubuntu that copies the script inside and runs it, and the script runs until it receives the SIGTERM signal from the operating system. We build the image.

	
< > Input
Python
!docker build -t ubuntu:loop ./Dockerfile_loop
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : COPY ["loop.sh", "/"]
---&gt; 89f2bbd25a88
Step 3/3 : CMD /loop.sh
---&gt; Running in ff52569c35fd
Removing intermediate container ff52569c35fd
---&gt; feb091e4efa3
Successfully built feb091e4efa3
Successfully tagged ubuntu:loop
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

We run the container

docker run -d --name looper ubuntu:loop bash
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
8a28f8cc9892213c4e0603dfdde320edf52c091b82c60510083549a391cd6645

We check and see that the container is running

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 4 seconds ago Up 3 seconds looper

We tried to stop the container with docker stop looper. docker stop tries to stop the container by sending it the SIGTERM signal.

	
< > Input
Python
%%time
!docker stop looper
Copied
>_ Output
			
looper
CPU times: user 89.2 ms, sys: 21.7 ms, total: 111 ms
Wall time: 10.6 s

This has taken about 10 seconds to stop, when it should have been immediate. This is because stop sent the SIGTERM command to stop the container, but since it did not stop, after a while it sent a SIGKILL to force it to stop. Let's see what happens if we list the containers

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 23 seconds ago Exited (137) 2 seconds ago looper

We can see that the Exited signal is 137, which corresponds to SIGKILL; that is, Docker had to force the shutdown.

Let's delete the container and run it again

	
< > Input
Python
!docker rm looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
84bc37f944d270be5f84a952968db2b8cf5372c61146d29383468198ceed18fd

If we now try to stop the container with docker kill looper

	
< > Input
Python
%%time
!docker kill looper
Copied
>_ Output
			
looper
CPU times: user 9.1 ms, sys: 857 µs, total: 9.96 ms
Wall time: 545 ms

We see that the time is about 500 ms, that is, Docker stopped it at some point by sending it the SIGKILL command. Because kill does not send SIGTERM, and if the container has not stopped after some time, it sends SIGKILL; what it does is send SIGKILL from the start.

If we look at the containers, we see that the exit signal is the same, 137

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84bc37f944d2 ubuntu:loop "/bin/sh -c /loop.sh" 6 seconds ago Exited (137) 2 seconds ago looper

This is not the correct way to shut down a container, because when we want to shut down the container, it should be done using the SIGTERM signal, so that it finishes processing whatever it was doing and then shuts down

If we delete the container and run it again

	
< > Input
Python
!docker rm looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
b9d9f370cc0de7569eb09d0a85cd67e8ea6babc0754a517ccba5c5057f5cc50e

If we now see the processes that are running inside the container

	
< > Input
Python
!docker exec looper ps -ef
Copied
>_ Output
			
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:05 ? 00:00:00 /bin/sh -c /loop.sh
root 7 1 93 14:05 ? 00:00:02 bash /loop.sh
root 8 0 0 14:05 ? 00:00:00 ps -ef

Actually, the main process, the 1, is not /loop.sh but rather /bin/sh -c /loop.sh, that is, it is a child process of the shell. So when the SIGTERM signal arrived, it reached the shell, but it does not send it to its child processes, which is why it never reached loop.sh

To prevent this from happening, the Dockerfile needs to be changed to the following

*Dockerfile*:

FROM ubuntu:trusty
      COPY ["loop.sh", "/"]
      CMD ["/loop.sh"]    # before it was CMD /loop.sh

This form is called exec form, while the previous one is called shell form, so in the previous form the process runs as a child of the shell, whereas in the exec form the process we specify is executed. So we delete the container, rebuild it, and run the container again with the image

	
< > Input
Python
!docker rm -f looper
Copied
>_ Output
			
looper
	
< > Input
Python
!docker build -t ubuntu:loop ./Dockerfile_loop
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : COPY ["loop.sh", "/"]
---&gt; Using cache
---&gt; 89f2bbd25a88
Step 3/3 : CMD ["/loop.sh"]
---&gt; Running in 6b8d92fcd57c
Removing intermediate container 6b8d92fcd57c
---&gt; 35a7bb2b1892
Successfully built 35a7bb2b1892
Successfully tagged ubuntu:loop
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
	
< > Input
Python
!docker run -d --name looper ubuntu:loop
Copied
>_ Output
			
850ae70c071426850b28428ac60dcbf875c6d35d9b7cc66c17cf391a23392965

Yes, now I see the processes inside the container

	
< > Input
Python
!docker exec looper ps -ef
Copied
>_ Output
			
UID PID PPID C STIME TTY TIME CMD
root 1 0 88 14:14 ? 00:00:02 bash /loop.sh
root 7 0 0 14:14 ? 00:00:00 ps -ef

Now I see that the main process, 1, is /loop.sh

If I now try to stop the container

	
< > Input
Python
%%time
!docker stop looper
Copied
>_ Output
			
looper
CPU times: user 989 µs, sys: 7.55 ms, total: 8.54 ms
Wall time: 529 ms

We see that it takes longer. Let's look at the code where it stopped

	
< > Input
Python
!docker ps -a
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
850ae70c0714 ubuntu:loop "/loop.sh" About a minute ago Exited (0) 33 seconds ago looper

Executable Containerslink image 49

If we want a binary to run as an executable, in the dockerfile we need to specify the command in ENTRYPOINT and the command parameters in CMD, let's see it

Let's create a new folder where we will store the Dockerfile

	
< > Input
Python
!mkdir dockerfile_ping
Copied

Now we create a Dockerfile inside

	
< > Input
Python
!touch dockerfile_ping/Dockerfile
Copied

We write the following inside the Dockerfile

FROM ubuntu:trusty
      ENTRYPOINT [ "/bin/ping", "-c", "3" ]
      CMD [ "localhost" ]

We built the image

	
< > Input
Python
!docker build -t ubuntu:ping ./dockerfile_ping
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/3 : ENTRYPOINT [ "/bin/ping", "-c", "3" ]
---&gt; Using cache
---&gt; 1cebcfb542b1
Step 3/3 : CMD [ "localhost" ]
---&gt; Using cache
---&gt; 04ddc3de52a2
Successfully built 04ddc3de52a2
Successfully tagged ubuntu:ping
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

If we now run the image without passing it a parameter, the container will ping itself

	
< > Input
Python
!docker run --name ping_localhost ubuntu:ping
Copied
>_ Output
			
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.054 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2027ms
rtt min/avg/max/mdev = 0.041/0.051/0.058/0.007 ms

But if we now pass it a parameter, it will ping the address we tell it to.

	
< > Input
Python
!docker run --name ping_google ubuntu:ping google.com
Copied
>_ Output
			
PING google.com (216.58.209.78) 56(84) bytes of data.
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=1 ttl=111 time=3.93 ms
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=111 time=6.80 ms
64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=3 ttl=111 time=6.92 ms
--- google.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 3.930/5.886/6.920/1.383 ms

We remove the containers

	
< > Input
Python
!docker rm ping_localhost ping_google
Copied
>_ Output
			
ping_localhost
ping_google

The build contextlink image 50

Let's create a folder called dockerfile_contexto

	
< > Input
Python
!mkdir dokerfile_contexto
Copied

Now we create two files in it: a test.txt and the Dockerfile

	
< > Input
Python
!touch dokerfile_contexto/Dockerfile dokerfile_contexto/text.txt
Copied

We modify the Dockerfile and put the following:

FROM ubuntu:trusty
      COPY [".", "/"]

This will copy into the image everything that is in the folder where the Dockerfile is located. We build the image

	
< > Input
Python
!docker build -t ubuntu:contexto ./dokerfile_contexto
Copied
>_ Output
			
Sending build context to Docker daemon 2.56kB
Step 1/2 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/2 : COPY [".", "/"]
---&gt; 3ab79fdce389
Successfully built 3ab79fdce389
Successfully tagged ubuntu:contexto
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Let's see what's inside the container

	
< > Input
Python
!docker run --name ls ubuntu:contexto ls
Copied
>_ Output
			
Dockerfile
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
text.txt
tmp
usr
var

As we can see, there is the text.txt file. But it is possible that inside the folder that is in the same directory as the Dockerfile there are files or folders that we do not want to be copied into the image, for whatever reason, so just like in git we have .gitignore, in Docker we have .dockerignore, where we put the files or folders that we do not want to be taken into account when building

So we create a .dockerignore file

	
< > Input
Python
!touch dokerfile_contexto/.dockerignore
Copied

And inside we add the text.txt, and while we’re at it, the Dockerfile, which we don’t need inside the image

*.dockerignore*:

```Dockerfiletext.txt Dockerfiletext.txt ```

We delete the container we had created, rebuild it, and see what is inside the container

	
< > Input
Python
!docker rm ls
Copied
>_ Output
			
ls
	
< > Input
Python
!docker build -t ubuntu:contexto ./dokerfile_contexto
Copied
>_ Output
			
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM ubuntu:trusty
---&gt; 13b66b487594
Step 2/2 : COPY [".", "/"]
---&gt; 7a6689546da4
Successfully built 7a6689546da4
Successfully tagged ubuntu:contexto
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
	
< > Input
Python
!docker run --name ls ubuntu:contexto ls
Copied
>_ Output
			
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

We see that now neither Dockerfile nor text.txt are there. We delete the container

	
< > Input
Python
!docker rm ls
Copied
>_ Output
			
ls

Multi-stage buildlink image 51

At the end of a development, we do not want all the code to be in the image that is going to be deployed to production.

We can split the dockerfile into two, for example, the developer.Dockerfile and the production.Dockerfile, where development will include more things than production. When building them, using the -f option, we choose the dockerfile we want to use

docker build -t <tag> -f developer.Dockerfiledocker build -t <tag> -f production.Dockerfile

But so as not to have to create two Dockerfile files, Docker created multi-stage builds. With a single Dockerfile, we’re going to solve the problem

We create the folder where we are going to save the Dockerfile

	
< > Input
Python
!mkdir docker_multi_stage
Copied

And inside we create the Dockerfile file

	
< > Input
Python
!cd docker_multi_stage && touch Dockerfile
Copied

We edit the file, adding the following

# Stage 1: Generate the executable with Python based on Alpine
FROM python:3.9-alpine as build-stage
WORKDIR /app
# Install dependencies for PyInstaller
RUN apk add --no-cache gcc musl-dev libc-dev
# Generate hello.py
RUN echo 'print("Hello from Alpine!")' > hello.py
# Install PyInstaller
RUN pip install pyinstaller
# Use PyInstaller to create a standalone executable
RUN pyinstaller --onefile hello.py

# Stage 2: Run the executable in an Alpine image
FROM alpine:latest
WORKDIR /app
# Copy the executable from the build stage
COPY --from=build-stage /app/dist/hello .
# Default command to run the executable
CMD ["./hello"]

As can be seen, the Dockerfile is divided into two parts. On the one hand, work is done on the python:3.9-alpine image, which is called build-stage. And, on the other hand, we work on the alpine:latest image, which is a very lightweight Linux image and is widely used in production

We compiled it

	
< > Input
Python
!docker build -t maximofn/multistagebuild:latest ./docker_multi_stage
Copied
>_ Output
			
[+] Building 0.0s (0/2) docker:default
>_ Output
			
[+] Building 0.2s (4/6) docker:default
=&gt; [internal] load build definition from Dockerfile 0.0s
=&gt; =&gt; transferring dockerfile: 722B 0.0s
=&gt; [internal] load .dockerignore 0.0s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:latest 0.1s
=&gt; [internal] load metadata for docker.io/library/python:3.9-alpine 0.1s
...
=&gt; CACHED [stage-1 3/3] COPY --from=build-stage /app/dist/hello . 0.0s
=&gt; exporting to image 0.0s
=&gt; =&gt; exporting layers 0.0s
=&gt; =&gt; writing image sha256:7fb090d1495d00e892118b6bc3c03400b63a435fd4703 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multistagebuild:latest 0.0s

If we now look at the images we have

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multistagebuild latest 7fb090d1495d 8 minutes ago 13.6MB

Let's download the Python image to see how much it weighs

	
< > Input
Python
!docker pull python:3.9-alpine
Copied
>_ Output
			
3.9-alpine: Pulling from library/python
a8db6415: Already exists
d5e70e42: Already exists
3fe96417: Already exists
aa4dddbb: Already exists
518be9f7: Already exists Digest: sha256:6e508b43604ff9a81907ec17405c9ad5c13664e45a5affa2206af128818c7486
Status: Downloaded newer image for python:3.9-alpine
docker.io/library/python:3.9-alpine
	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multistagebuild latest 7fb090d1495d 9 minutes ago 13.6MB
python 3.9-alpine 6946662f018b 9 days ago 47.8MB

We can see that while our image weighs only 13.6 MB, the Python image with which the application was built weighs 47.8 MB. So we can draw two conclusions: with the first image, the Python one, the application was built, the executable was generated, and that executable is the one we use in the second image, the Alpine one. We can also see that although the first image used is Python, it is not downloaded to our system, since we had to download it ourselves

Well, all that’s left is to try it.

	
< > Input
Python
!docker run --rm --name multi_stage_build maximofn/multistagebuild
Copied
>_ Output
			
Hello from Alpine!

It works!

Compilaciones multiarquitecturalink image 52

Suppose we want to make an image that can run on a computer and on a Raspberry Pi. The computer will probably have a CPU with AMD64 architecture, while the Raspberry Pi has a CPU with ARM architecture. Therefore, we cannot create the same image for both. That is, when we create an image, we create it with a Dockerfile that usually starts like this

FROM ...

Therefore, the Dockerfile of the computer image could start like this

FROM ubuntu:latest

While the Raspberry one could start like this

FROM arm64v8/ubuntu:latest

We would need to create two Dockerfile files, build them, and use one image on the computer and another on the Raspberry Pi

To avoid having to check the computer architecture and determine which image we need to use, Docker creates the manifest, which, as its name suggests, is a manifest that indicates, depending on the CPU architecture we have, which image to use.

So let's see how to do this

First, we create a folder where we are going to create our Dockerfile files

	
< > Input
Python
!mkdir docker_multi_arch
Copied

Now we create the two Dockerfiles

	
< > Input
Python
!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64
Copied

We write the Dockerfile for AMD64

	
< > Input
Python
!cd docker_multi_arch && echo "FROM ubuntu:20.04" &gt;&gt; Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" &gt;&gt; Dockerfile_amd64
Copied
	
< > Input
Python
!cd docker_multi_arch && echo "FROM arm64v8/ubuntu:latest" &gt;&gt; Dockerfile_arm && echo "CMD echo 'Hello from ARM'" &gt;&gt; Dockerfile_arm
Copied

Now we combine the two images

	
< > Input
Python
!cd docker_multi_arch && docker build -t maximofn/multiarch:arm -f Dockerfile_arm .
Copied
>_ Output
			
[+] Building 0.0s (0/1) docker:default
[+] Building 0.2s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.1s
[+] Building 0.3s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.2s
[+] Building 0.5s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s
[+] Building 0.6s (2/3) docker:default
=&gt; [internal] load build definition from Dockerfile_amd64 0.1s
=&gt; =&gt; transferring dockerfile: 89B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.5s
...
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load build definition from Dockerfile_arm 0.0s
=&gt; =&gt; transferring dockerfile: 94B 0.0s
=&gt; [internal] load metadata for docker.io/arm64v8/ubuntu:latest 1.8s
=&gt; [auth] arm64v8/ubuntu:pull token for registry-1.docker.io 0.0s
=&gt; CACHED [1/1] FROM docker.io/arm64v8/ubuntu:latest@sha256:94d12db896d0 0.0s
=&gt; exporting to image 0.0s
=&gt; =&gt; exporting layers 0.0s
=&gt; =&gt; writing image sha256:a9732c1988756dc8e836fd96e5c9512e349c97ea5af46 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multiarch:arm 0.0s

Let's see what we have in the two compiled images

	
< > Input
Python
!docker image ls
Copied
>_ Output
			
REPOSITORY TAG IMAGE ID CREATED SIZE
maximofn/multiarch arm a9732c198875 4 weeks ago 69.2MB
maximofn/multiarch amd64 5b612c83025f 6 weeks ago 72.8MB

We see that we have built the two images. To be able to create a manifest, we first need to upload the images to Docker Hub, so we upload them

	
< > Input
Python
!docker push maximofn/multiarch:amd64
Copied
>_ Output
			
The push refers to repository [docker.io/maximofn/multiarch]
82bdeb5f: Mounted from library/ubuntu amd64: digest: sha256:30e820f2a11a24ad4d8fb624ae485f7c1bcc299e8cfc72c88adce1acd0447e1d size: 529
	
< > Input
Python
!docker push maximofn/multiarch:arm
Copied
>_ Output
			
The push refers to repository [docker.io/maximofn/multiarch]
>_ Output
			
eda53374: Layer already exists arm: digest: sha256:6ec5a0752d49d3805061314147761bf25b5ff7430ce143adf34b70d4eda15fb8 size: 529

If I go to my Docker Hub, I can see that my image maximofn/multiarch has the amd64 and arm tags

docker_multi_arch_tags

Now we are going to create the manifest based on these two images

	
< > Input
Python
!docker manifest create maximofn/multiarch:latest maximofn/multiarch:amd64 maximofn/multiarch:arm
Copied
>_ Output
			
Created manifest list docker.io/maximofn/multiarch:latest

Once created, we need to indicate the CPU architectures to which each one corresponds.

	
< > Input
Python
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:amd64 --os linux --arch amd64
Copied
	
< > Input
Python
!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:arm64 --os linux --arch arm64
Copied
>_ Output
			
manifest for image maximofn/multiarch:arm64 does not exist in maximofn/multiarch:latest

Once created and annotated, we can upload the manifest to Docker Hub

	
< > Input
Python
!docker manifest push maximofn/multiarch:latest
Copied
>_ Output
			
sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304

If I look again now at the tags my maximofn/multiarch image has, I also see the latest one

docker_multi_arch_tags_manifest

Now, whether I want to use my image from a machine with an AMD64 CPU or an ARM CPU when using FROM maximofn/multiarch:latest, Docker will check the CPU architecture and pull the amd64 tag or the arm tag. Let's see it; if I run the image from my computer, I get

	
< > Input
Python
!docker run maximofn/multiarch:latest
Copied
>_ Output
			
Unable to find image 'maximofn/multiarch:latest' locally
>_ Output
			
latest: Pulling from maximofn/multiarch
Digest: sha256:7cef0de10f7fa2b3b0dca0fbf398d1f48af17a0bbc5b9beca701d7c427c9fd84
Status: Downloaded newer image for maximofn/multiarch:latest
Hello from amd64

Since he doesn't have it, he downloads it

If I now connect via SSH to a Raspberry Pi and try the same thing, I get

raspberry@raspberrypi:~ $ docker run maximofn/multiarch:latest
Unable to find image 'maximofn/multiarch:latest' locally
latest: Pulling from maximofn/multiarch
Digest: sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Status: Downloaded newer image for maximofn/multiarch:latest
Hello from ARM

Hello from ARM appears because the Raspberry has a processor with ARM architecture

As can be seen, each machine has downloaded the image it needed

Advanced Dockerfile Writinglink image 53

We already saw how to write Dockerfiles correctly, but there is one more thing we can do now that we know about multi-stage builds, and that is to create one container to build the executable and another smaller one to run it

We came to the conclusion that a good Dockerfile could be this one

FROM python:3.9.18-alpine
      WORKDIR /sourceCode/sourceApp
      COPY ./sourceCode/sourceApp .
      CMD ["python3", "app.py"]

Let's now create an executable in a builder container and run it in a smaller one.

FROM python:3.9.18-alpine as builder
WORKDIR /sourceCode/sourceApp
RUN apk add --no-cache gcc musl-dev libc-dev && pip install pyinstaller
COPY ./sourceCode/sourceApp .
RUN pyinstaller --onefile app.py

FROM alpine:3.18.3
WORKDIR /sourceCode/sourceApp
COPY --from=builder /sourceCode/sourceApp/dist/app .
CMD ["./app"]

We created the Python code in the necessary path

	
< > Input
Python
!mkdir multistagebuild/sourceCode
!mkdir multistagebuild/sourceCode/sourceApp
!touch multistagebuild/sourceCode/sourceApp/app.py
!echo 'print("Hello from Alpine!")' &gt; multistagebuild/sourceCode/sourceApp/app.py
Copied

Now compiling the image

	
< > Input
Python
!docker build -t maximofn/multistagebuild:alpine-3.18.3 ./multistagebuild
Copied
>_ Output
			
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/1) docker:default
[+] Building 0.2s (3/5) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.1s
=&gt; [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.1s
=&gt; [auth] library/alpine:pull token for registry-1.docker.io 0.0s
[+] Building 0.3s (3/5) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.2s
=&gt; [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.2s
=&gt; [auth] library/alpine:pull token for registry-1.docker.io 0.0s
[+] Building 0.5s (4/6) docker:default
=&gt; [internal] load build definition from Dockerfile 0.1s
=&gt; =&gt; transferring dockerfile: 357B 0.0s
=&gt; [internal] load .dockerignore 0.1s
=&gt; =&gt; transferring context: 2B 0.0s
=&gt; [internal] load metadata for docker.io/library/alpine:3.18.3 0.4s
...
=&gt; exporting to image 0.1s
=&gt; =&gt; exporting layers 0.1s
=&gt; =&gt; writing image sha256:8a22819145c6fee17e138e818610ccf46d7e13c786825 0.0s
=&gt; =&gt; naming to docker.io/maximofn/multistagebuild:alpine-3.18.3 0.0s

We run it

	
< > Input
Python
!docker run --rm --name multi_stage_build maximofn/multistagebuild:alpine-3.18.3
Copied
>_ Output
			
Hello from Alpine!

The image maximofn/multistagebuild:alpine-3.18.3 only weighs 13.6 MB

Difference between RUN, CMD and ENTRYPOINTlink image 54

RUNlink image 55

The RUN command is the simplest; it simply executes a command during image build time. For example, if we want to install a package in the image, we do so using RUN.

Therefore, it is important: RUN is executed at image build time, not when the container runs

CMDlink image 56

The CMD command is the command that runs when the container is executed. For example, if we want the container to run a command when it is executed, we do it through CMD. For example, if we have a Python application in a container, with CMD we can indicate that when the container is run, it should run the Python application.

In this way, when the container starts, the Python application will be executed. That is, if we run docker run <image> the Python application will be executed. But CMD allows us to override the command that is executed when the container starts; for example, if we run docker run <image> bash, bash will be executed instead of the Python application.

ENTRYPOINTlink image 57

The ENTRYPOINT command is similar to the CMD command, but with one difference: ENTRYPOINT is not meant to be overridden. In other words, if we have a Python application in a container, with ENTRYPOINT we can tell it that when the container is run it should execute the Python application. But if we run docker run <image> bash, the Python application will be executed, not bash.

A very common use of ENTRYPOINT is when we want the container to be an executable, for example, if we want the container to be an executable for a version of Python that we do not have on our host, because, for example, we want to test the new version of Python that has been released, we can do:

FROM python:3.9.18-alpine
      ENTRYPOINT ["python3"]

In this way, when the container starts, Python will be executed. That is, if we run docker run <image>, Python will be executed. But ENTRYPOINT allows us to override the command that is executed when the container starts; for example, if we run docker run <image> myapp.py, python3 myapp.py will be executed inside the container. This way, we can test our Python application on the new version of Python

Changes in a containerlink image 58

With docker diff we can see the differences between the container and the image, which is the same as the difference in the container from when it was created until now

Let's run a container and create a file inside it

	
< > Input
Python
!docker run --rm -it --name ubuntu-20.04 ubuntu:20.04 bash
Copied
>_ Output
			
root@895a19aef124:/# touch file.txt

Now we can see the difference

	
< > Input
Python
!docker diff ubuntu-20.04
Copied
>_ Output
			
C /root
A /root/.bash_history
A /file.txt

A means that it has been added, C means that it has been changed, and D means that it has been deleted

Docker in Dockerlink image 59

Suppose we have containers that need to start up or shut down other containers. This is achieved as follows

Since on Linux everything is a file and the host communicates with Docker via a socket. So for Linux, that socket is a file. So if we mount that socket as a file into the container, it will be able to talk to Docker

First, let's set up a container with Ubuntu.

	
< > Input
Python
!docker run -d --name ubuntu ubuntu:latest tail -f /dev/null
Copied
>_ Output
			
144091e4a3325c9068064ff438f8865b40f944af5ce649c7156ca55a3453e423

Let's mount the container that will be able to talk to Docker by mounting the /var/run/docker.sock folder

$ docker run -it --rm --name main -v /var/run/docker.sock:/var/run/docker.sock docker:19.03.12
/ #

We have entered a container, and if inside we run docker ps

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds main
144091e4a332 ubuntu:latest "tail -f /dev/null" 19 seconds ago Up 18 seconds ubuntu

As we can see, inside Docker we can see the host's containers

We can run a new container

# docker run -d --name ubuntu_from_main ubuntu:latest tail -f /dev/null
362654a72bb0fb047c13968707a6f16b87fed7ce051eb5c1a146b15828589a1a
/ #

And if we look at the containers again

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES362654a72bb0 ubuntu:latest "tail -f /dev/null" 3 seconds ago Up 3 seconds ubuntu_from_main
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" About a minute ago Up About a minute main
144091e4a332 ubuntu:latest "tail -f /dev/null" 2 minutes ago Up About a minute ubuntu

But if we now run a new host terminal, we will see the container created from inside the container

	
< > Input
Python
!docker ps
Copied
>_ Output
			
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
362654a72bb0 ubuntu:latest "tail -f /dev/null" About a minute ago Up About a minute ubuntu_from_main
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes main
144091e4a332 ubuntu:latest "tail -f /dev/null" 3 minutes ago Up 3 minutes ubuntu

Everything we do from the main container will be reflected on the host

This has the advantage that we can install programs in a container that has access to the host so we don't have to install them on the host. For example dive is a tool for exploring containers, but if you don't want to install it on the host you can install it in a container with access to the host, so from that main container you can explore the rest of the containers without having to install it on the host

Continue reading

Last posts -->

Have you seen these projects?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Mobile personal training app with AI assistant, exercise library, workout tracking, diet and body measurements

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversational for cooks of hotels and restaurants. A cook, kitchen manager or room service of a hotel or restaurant can talk to the chatbot to get information about recipes and menus. But it also implements agents, with which it can edit or create new recipes or menus

View all projects -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to watch any talk?

Last talks -->

Do you want to improve with these tips?

Last tips -->

Use this locally

Hugging Face spaces allow us to run models with very simple demos, but what if the demo breaks? Or if the user deletes it? That's why I've created docker containers with some interesting spaces, to be able to use them locally, whatever happens. In fact, if you click on any project view button, it may take you to a space that doesn't work.

Flow edit

Flow edit Flow edit

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
View all containers -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to train your model with these datasets?

short-jokes-dataset

HuggingFace

Dataset with jokes in English

Use: Fine-tuning text generation models for humor

231K rows 2 columns 45 MB
View on HuggingFace →

opus100

HuggingFace

Dataset with translations from English to Spanish

Use: Training English-Spanish translation models

1M rows 2 columns 210 MB
View on HuggingFace →

netflix_titles

HuggingFace

Dataset with Netflix movies and series

Use: Netflix catalog analysis and recommendation systems

8.8K rows 12 columns 3.5 MB
View on HuggingFace →
View more datasets -->