Na primeira parte aprendemos a gerenciar contêineres, os dados e volumes, as imagens, a criar aplicações com Docker e a usar GPUs. Neste segundo capítulo damos o salto para **Docker Compose** para orquestrar vários contêineres e para uma série de **temas avançados** do Docker. 🐳
Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.
📚 **Esta entrada faz parte da série _Guia de Docker_**, dividida em dois capítulos que são lidos em ordem:
> * Parte 1: Contenedores, imágenes y aplicaciones
* 👉 **Parte 2: Docker Compose y temas avanzados**
Docker compose
Docker compose vs docker-compose
docker-compose foi uma ferramenta que foi criada para ajudar na manutenção de imagens e contêineres e tinha que ser instalada separadamente do docker. No entanto, o docker a incorporou em suas últimas versões e já não é necessário instalá-la; porém, para usá-la, em vez de usar o comando docker-compose é preciso usar o comando docker compose. Em muitos sites você encontrará informações com docker-compose, mas ao instalar o docker já virá instalado docker compose, por isso tudo o que se podia fazer com docker-compose é compatível com docker compose
Docker compose
Docker Compose é uma ferramenta do Docker que faz tudo o que vimos até agora, mas poupando-nos tempo e esforço. Editando um arquivo .yml, podemos dizer ao Docker Compose que crie todos os contêineres que quisermos.
Para usá-lo uma vez, não haverá muita diferença entre escrever todos os comandos que vimos antes ou escrever o arquivo .yml, mas quando você quiser voltar a ter a mesma configuração de contêineres em funcionamento, basta chamar o arquivo .yml para recriar toda a configuração
Vamos criar uma pasta onde guardaremos os arquivos do Docker Compose
InputPython!mkdir dockerComposeFilesCopied
Criamos o arquivo .yml dentro
InputPython!touch dockerComposeFiles/docker-compose.ymlCopied
Um arquivo Docker Compose tem que começar pela versão
version: "<v.v>"No momento de escrever isto, a última versão é a 3.8, então escrevemos essa.
*docker-compose.yml*:
version: "3.8"A seguir indicam-se os serviços, que são os contentores. Em cada serviço é necessário especificar a imagem e, além disso, podem ser adicionados outros parâmetros como portas, variáveis de ambiente, etc.
serviços:
container1:
imagem: ubuntu
container2:
imagem: ubuntuO docker-compose.yml ficaria assim:
version: "3.8"
services:
container1:
image: ubuntu
container2:
image: ubuntuUma vez que criamos o arquivo, no seu caminho, podemos executar tudo mediante o comando docker compose up, mas além disso, adicionando a opção -d, faremos com que ele rode em segundo plano
InputPython!cd dockerComposeFiles && docker compose up -dCopied
[+] 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
Se observarmos, criou dois contêineres dockercomposefiles-container1-1 e dockercomposefiles-container2-1 e a rede que os une dockercomposefiles_default
Vamos apagar os dois contêineres
InputPython!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1Copied
dockercomposefiles-container1-1dockercomposefiles-container2-1
E apagamos a rede que foi criada
InputPython!docker network rm dockercomposefiles_defaultCopied
dockercomposefiles_default
Vamos tentar fazer o que fizemos antes com o que sabemos até agora. Criamos uma nova imagem que venha com ping instalada
*Dockerfile*:
FROM ubuntu:20.04
RUN apt update
RUN apt install iputils-ping -yE a compilamos
InputPython!docker build -t ubuntu:ping ./dockerImagesCopied
Sending build context to Docker daemon 2.048kBStep 1/3 : FROM ubuntu:20.04---> a0ce5a295b63Step 2/3 : RUN apt update---> Running in 3bd5278d39b4WARNING: 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 c3d32aa9de02Successfully tagged ubuntu:pingUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Verificamos que foi criado
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu test a78cf3ea16d8 25 hours ago 77.8MBnginx latest 2d389e545974 33 hours ago 142MBubuntu latest 2dc39ba059dc 12 days ago 77.8MBubuntu 20.04 a0ce5a295b63 12 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Nós alteramos a tag
InputPython!docker tag ubuntu:ping maximofn/ubuntu:pingCopied
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu ping c3d32aa9de02 About a minute ago 112MBmaximofn/ubuntu test c3d32aa9de02 About a minute ago 112MBnginx latest 2d389e545974 33 hours ago 142MBubuntu latest 2dc39ba059dc 12 days ago 77.8MBubuntu 20.04 a0ce5a295b63 12 days ago 72.8MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
Editamos o arquivo Docker Compose para que pegue as imagens que acabamos de criar
*docker-compose.yml*:
version: "3.8"
services:
container1:
image: maximofn/ubuntu:ping
container2:
image: maximofn/ubuntu:pingE além disso, dizemos para ele executar uma operação nula
O docker-compose.yml ficaria assim:
version: "3.8"
serviços:
container1:
image: ubuntu
command: tail -f /dev/null
container2:
image: ubuntu
comando: tail -f /dev/nullNós o levantamos
InputPython!cd dockerComposeFiles && docker compose up -dCopied
[+] 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
Vemos os contêineres que estão rodando
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES935939e5a75d maximofn/ubuntu:ping "tail -f /dev/null" 15 seconds ago Up 13 seconds dockercomposefiles-container2-1f9138d7064dd maximofn/ubuntu:ping "tail -f /dev/null" 25 seconds ago Up 13 seconds dockercomposefiles-container1-1
Os dois contêineres estão em execução; agora entramos em um deles e tentamos fazer ping no outro
$ 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 do dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=3 ttl=64 time=0.049 ms
64 bytes de dockercomposefiles-container2-1.dockercomposefiles_default (172.21.0.3): icmp_seq=4 ttl=64 time=0.075 ms^C
--- estatísticas de ping do dockercomposefiles-container2-1 ---
4 pacotes transmitidos, 4 recebidos, 0% de perda de pacotes, tempo 3068ms
rtt min/avg/max/mdev = 0.049/0.070/0.110/0.025 msComo vemos, podemos fazer ping, criamos corretamente a imagem com ping instalado. Além disso, no docker-compose fizemos com que seja executada uma não operação para que os contêineres estejam em execução
Apagamos os dois contêineres e a rede que criamos
InputPython!docker rm -f dockercomposefiles-container1-1 dockercomposefiles-container2-1Copied
dockercomposefiles-container1-1dockercomposefiles-container2-1
InputPython!docker network rm dockercomposefiles_defaultCopied
dockercomposefiles_default
Como o Docker Compose nomeia os contêineres
Se observamos, os contêineres que o Docker cria se chamam dockercomposefiles-container1-1 e dockercomposefiles-container2-1. Isso acontece porque a pasta em que está o arquivo do Docker Compose fica em uma pasta chamada dockerComposeFiles, por isso a primeira parte do nome dos contêineres é dockercomposefiles, em seguida aparece o nome do serviço que demos no arquivo Docker Compose (container1 e container2) e, por último, um número para poder criar mais se for necessário
De forma semelhante, isso ocorre com o nome da rede que foi criada dockercomposefiles_default
Logs no docker compose
Vamos agora alterar o arquivo Docker Compose, nas linhas em que tínhamos command: tail -f /dev/null, vamos colocar command: ping 0.0.0.0
E além disso, dizemos a ele para executar uma não operação
O docker-compose.yml ficaria assim:
version: "3.8"
serviços:
container1:
image: ubuntu
comando: ping 0.0.0.0
container2:
image: ubuntu
command: ping 0.0.0.0Fazemos isso para que cada contêiner esteja disparando o ping constantemente, assim simulamos alguns logs
Se executarmos novamente o docker compose
InputPython!cd dockerComposeFiles && docker compose up -dCopied
[+] 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
Agora podemos ver os logs dos dois contêineres por meio do comando docker compose logs
InputPython!cd dockerComposeFiles && docker compose logsCopied
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 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 msdockercomposefiles-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 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 msdockercomposefiles-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 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=215 ttl=64 time=0.021 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=216 ttl=64 time=0.020 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=217 ttl=64 time=0.049 ms
Como vemos, podemos ver os logs dos dois contêineres, mas no caso de querer ver apenas os de um, podemos especificar o **nome do serviço**
InputPython!cd dockerComposeFiles && docker compose logs container1Copied
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 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.023 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.031 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.022 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.032 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.029 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.031 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.024 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.029 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.032 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.034 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.028 msdockercomposefiles-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 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=333 ttl=64 time=0.030 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=334 ttl=64 time=0.033 msdockercomposefiles-container1-1 | 64 bytes from 127.0.0.1: icmp_seq=335 ttl=64 time=0.036 ms
InputPython!cd dockerComposeFiles && docker compose logs container2Copied
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 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.025 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.022 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.021 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.030 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.026 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.028 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.027 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.039 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.035 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.036 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.032 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=21 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=22 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=23 ttl=64 time=0.035 msdockercomposefiles-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 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=341 ttl=64 time=0.033 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=342 ttl=64 time=0.034 msdockercomposefiles-container2-1 | 64 bytes from 127.0.0.1: icmp_seq=343 ttl=64 time=0.036 ms
Se queremos ver os logs continuamente, podemos adicionar a opção -f: docker compose logs -f <service name>
Se eu tiver feito um docker compose com mais de dois serviços, quando quiser ver os logs de vários serviços, basta adicionar mais nomes ao comando: docker compose logs <name service 1> <name service 2> ...
Exec serviços
Como vimos, por meio do comando exec podemos entrar em um contêiner indicando o nome do contêiner, o comando que se quer executar e a opção -it. Com o Docker Compose isso é mais simples, pois basta o nome do serviço e o comando, mas não é necessária a opção -it já que o Docker Compose a assume por padrão.
$ docker compose exec container1 bash
root@a7cf282fe66c:/#Parando docker compose
Quando terminamos de trabalhar, com um único comando (stop), o Docker Compose para tudo; não é necessário parar um a um cada contêiner.
InputPython!cd dockerComposeFiles && docker compose stopCopied
[+] 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
Como se pode ver, o docker compose parou os dois contêineres, mas não os apagou, nem apagou a rede
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1e6c1dd9adb2 maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container2-1a7cf282fe66c maximofn/ubuntu:ping "ping 0.0.0.0" 16 minutes ago Exited (137) 25 seconds ago dockercomposefiles-container1-1
InputPython!docker network lsCopied
NETWORK ID NAME DRIVER SCOPE13cc632147f3 bridge bridge locald4a2f718cd83 dockercomposefiles_default bridge localda1f5f6fccc0 host host locald3b0d93993c0 none null local
Docker compose como ferramenta de desenvolvimento
Assim como vimos antes, para poder desenvolver, o ideal seria compartilhar a pasta que contém o código com o serviço. Isso, com o Docker Compose, é feito adicionando a etiqueta volumes ao arquivo docker compose. Primeiro temos que adicionar o caminho da pasta onde está o código no host e depois o caminho no contêiner.
*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.0Como se pode ver, o caminho da pasta do host eu o coloquei relativo
Se levantarmos o Docker Compose
InputPython!cd dockerComposeFiles && docker compose up -dCopied
[+] 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
Se entrarmos no contêiner, podemos ver o que há dentro do arquivo text.txt
$ docker compose exec container1 bash
root@c8aae9d619d3:/# ls dockerContainerFolder/
bindFile.txt fileExtract.txt text.txt
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
olá contenedorSe agora o abrirmos no host, escrevemos hola host e voltamos a ver no contêiner
root@c8aae9d619d3:/# cat dockerContainerFolder/text.txt
olá hostE agora ao contrário, se o modificarmos no contêiner
root@c8aae9d619d3:/# echo hola compose > dockerContainerFolder/text.txt
root@c8aae9d619d3:/# exit
exitSe o virmos a partir do host, devemos obter hola compose
InputPython!cat dockerHostFolder/text.txtCopied
hola compose
Exposição de portas no docker compose
Também podemos configurar as portas no arquivo do Docker Compose, por meio da etiqueta ports, indicando a porta do host e, em seguida, o IP do serviço
portas:
- <porta do host>:<porta do serviço>Docker compose em equipe - docker override
Se somos um grupo de pessoas desenvolvendo sobre Docker com Docker Compose, é provável que muitas pessoas estejam alterando o arquivo Docker Compose, o que pode fazer com que ele não sincronize bem e haja conflitos.
Para resolver isso, o Docker oferece uma ferramenta chamada Docker Override. Dessa forma, pode haver um arquivo Docker Compose base e cada um pode modificá-lo por meio do Docker Override.
Para fazer isso, agora temos que criar um arquivo chamado docker-compose.override.yml, que será o que poderemos editar
InputPython!touch dockerComposeFiles/docker-compose.override.ymlCopied
Se agora tentarmos levantar o Docker Compose, vamos receber um erro
InputPython!cd dockerComposeFiles && docker compose up -dCopied
Top-level object must be a mapping
E isto é porque o Docker Compose detectou que há um arquivo chamado docker-compose.override.yml e que ele está vazio, então vamos editá-lo. O arquivo docker-compose.override.yml serve para editar o arquivo docker-compose.yml, então, por exemplo, se quisermos fazer uma alteração no serviço container2 para adicionar um volume, escreveríamos assim o arquivo docker-compose.override.yml
*docker-compose.override.yml*:
version: "3.8"
serviços:
container2:
volumes:
- ../dockerHostFolder/:/dockerOverrideFolderNote que a pasta compartilhada no serviço eu a chamei dockerOverrideFolder, então vamos subir o docker compose e ver se conseguimos ver essa pasta no contêiner container2
InputPython!cd dockerComposeFiles && docker compose up -dCopied
[+] 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
Vemos que demorou 10 segundos em montar o serviço container2, isso porque ele esteve aplicando as alterações
$ docker compose exec container2 bash
root@d8777a4e611a:/# ls dockerOverrideFolder/
bindFile.txt fileExtract.txt text.txt
root@d8777a4e611a:/# cat dockerOverrideFolder/text.txtolá compose
root@d8777a4e611a:/# exit
Baixamos o Compose e apagamos os contêineres e a rede criada
InputPython!cd dockerComposeFiles && docker compose downCopied
[+] 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
Neste caso, apenas com down o Docker Compose parou e apagou tudo, já que, como vemos nos contêineres e na rede, aparece Removed
Reiniciar o Docker Compose
Ao escrever um docker compose, podemos adicionar a etiqueta restart para que, se o contêiner cair, ele seja reiniciado automaticamente
reiniciar: sempreDessa forma, se o contêiner cair, ele será reiniciado automaticamente. Se quisermos que ele seja reiniciado apenas um número de vezes, podemos adicionar a opção on-failure
restart: on-failure:<number>Agora o contêiner será reiniciado um número de vezes, mas se cair mais vezes, não será reiniciado. Se quisermos que ele seja reiniciado sempre, podemos adicionar a opção unless-stopped
restart: unless-stoppedAgora o contêiner será reiniciado sempre, a menos que seja parado manualmente
Docker avançado
Administrar ambiente de trabalho
Eliminação de contêineres desligados
Depois de passar um tempo desenvolvendo, podemos ter vários contêineres desligados, mas guardados no computador. Isso no final ocupa memória, então com docker container prune podemos eliminar todos os que estão parados
InputPython!docker run ubuntuCopied
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESeffcee24f54a ubuntu "bash" 37 seconds ago Exited (0) 36 seconds ago musing_rosalind
$ docker container prune
AVISO! Isto removerá todos os contêineres parados.
Tem certeza de que deseja continuar? [y/N] y
Containers excluídos:
effcee24f54aab22e34fdea2465b3b7af132d8c627e5432ba3e915a370876977
Espaço recuperado total: 0BNeste caso, economizamos 0 bytes, mas, no caso de deixar contêineres desligados após muito desenvolvimento, com certeza a economia de memória será maior
Exclusão de todos os contêineres
No caso de termos contêineres em execução, podemos eliminar todos os contêineres por meio de outro comando
O comando docker ps -q nos devolve a ID de todos os contêineres, pelo que com o comando docker rm -f $(docker ps -aq) pararemos e eliminaremos todos
InputPython!docker run -d ubuntu tail -f /dev/nullCopied
c22516186ef7e3561fb1ad0d508a914857dbc61274a218f297c4d80b1fc33863
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESc22516186ef7 ubuntu "tail -f /dev/null" About a minute ago Up About a minute agitated_knuth
InputPython!docker rm -f $(docker ps -aq)Copied
c22516186ef7
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Apagamento total
Como vimos, o Docker também cria redes, imagens, volumes, etc., então com o comando docker system prune podemos apagar todos os contêineres parados, todas as redes que não estejam sendo usadas por pelo menos um contêiner, as imagens duplicadas e o que estiver duplicado no cache de compilação
$ docker system prune
AVISO! Isto removerá:
- todos os contêineres parados
- todas as redes não usadas por pelo menos um contêiner
- todas as imagens pendentes
- todo o cache de compilação pendente
Tem certeza de que deseja continuar? [y/N] y
Espaço recuperado total: 0BAssim como antes, não foi economizado muito espaço, mas depois de muito tempo de desenvolvimento, a economia será considerável
Uso de recursos do host por parte de contêineres
Por exemplo, ao criar um contêiner, podemos limitar a RAM que o host pode usar por meio da opção --memory
InputPython!docker run -d --memory 1g ubuntu tail -f /dev/nullCopied
d84888eafe531831ef8915d2270422365adec02678122bf59580e2da782e6972
Mas com docker ps não temos acesso aos recursos que o contêiner está consumindo
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd84888eafe53 ubuntu "tail -f /dev/null" 35 seconds ago Up 34 seconds musing_ritchie
Para isso temos o comando docker stats
$ docker stats
CONTAINER ID NOME CPU % USO DE MEMÓRIA / LIMITE MEM % NET I/O BLOCK I/O PIDS
d84888eafe53 musing_ritchie 0.00% 540KiB / 1GiB 0.05% 5.62kB / 0B 0B / 0B 1Isso é muito útil se quisermos simular um ambiente com um limite de RAM
Parando contêineres corretamente: SHELL vs EXEC
Como explicamos, quando atribuimos um processo a um contêiner, quando esse processo termina, o contêiner para, mas às vezes podemos nos deparar com problemas com isso. Vamos criar uma nova pasta chamada Dockerfile_loop
InputPython!mkdir Dockerfile_loopCopied
Agora vamos criar um arquivo chamado loop.sh dentro de Dockerfile_loop
InputPython!touch Dockerfile_loop/loop.shCopied
E vamos escrever o seguinte dentro de loop.sh
#!/usr/bin/env bash
trap "exit 0" SIGTERM
while true; do :; doneSe eu executar este script no host, ele continuará sendo executado até que eu pressione CTRL+C
./loop
^CAgora vamos criar um arquivo Dockerfile dentro de Dockerfile_loop
InputPython!touch Dockerfile_loop/DockerfileCopied
*Dockerfile*:
FROM ubuntu:trusty
COPY ["loop.sh", "/"]CMD /loop.shVamos criar uma imagem baseada no Ubuntu que copia o script para dentro e o executa, e o script é executado até receber o sinal SIGTERM do sistema operacional. Compilamos a imagem
InputPython!docker build -t ubuntu:loop ./Dockerfile_loopCopied
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : COPY ["loop.sh", "/"]---> 89f2bbd25a88Step 3/3 : CMD /loop.sh---> Running in ff52569c35fdRemoving intermediate container ff52569c35fd---> feb091e4efa3Successfully built feb091e4efa3Successfully tagged ubuntu:loopUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Executamos o contêiner
docker run -d --name looper ubuntu:loop bashInputPython!docker run -d --name looper ubuntu:loopCopied
8a28f8cc9892213c4e0603dfdde320edf52c091b82c60510083549a391cd6645
Verificamos e vemos que o contêiner está em execução
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 4 seconds ago Up 3 seconds looper
Tentamos parar o contêiner com docker stop looper. O Docker stop tenta parar o contêiner enviando o sinal SIGTERM.
InputPython%%time!docker stop looperCopied
looperCPU times: user 89.2 ms, sys: 21.7 ms, total: 111 msWall time: 10.6 s
Isto demorou cerca de 10 segundos para parar, quando deveria ser imediato. Isso ocorre porque stop enviou a ordem SIGTERM para que o contêiner parasse, mas como ele não parava, depois de um tempo enviou um SIGKILL para forçar a interrupção. Vamos ver o que acontece, se listarmos os contêineres
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES8a28f8cc9892 ubuntu:loop "/bin/sh -c /loop.sh" 23 seconds ago Exited (137) 2 seconds ago looper
Podemos ver que o sinal de Exited é 137, isso equivale a SIGKILL, ou seja, o Docker teve que forçar o encerramento.
Vamos excluir o contêiner e executá-lo novamente
InputPython!docker rm looperCopied
looper
InputPython!docker run -d --name looper ubuntu:loopCopied
84bc37f944d270be5f84a952968db2b8cf5372c61146d29383468198ceed18fd
Se agora tentarmos parar o contêiner com docker kill looper
InputPython%%time!docker kill looperCopied
looperCPU times: user 9.1 ms, sys: 857 µs, total: 9.96 msWall time: 545 ms
Vemos que o tempo é de uns 500 ms, ou seja, o docker o parou num momento enviando-lhe a ordem SIGKILL. Porque kill não envia SIGTERM e, se em um tempo o contêiner não tiver parado, envia SIGKILL, o que faz é enviar SIGKILL desde o início.
Se virmos os contêineres, vemos que o sinal de saída é o mesmo, 137
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES84bc37f944d2 ubuntu:loop "/bin/sh -c /loop.sh" 6 seconds ago Exited (137) 2 seconds ago looper
Esta não é a maneira correta de desligar um contêiner, porque quando quisermos desligar o contêiner, isso deve ser feito por meio do sinal SIGTERM, para que ele termine de processar o que estiver fazendo e então se desligue
Se apagarmos o contêiner e o executarmos novamente
InputPython!docker rm looperCopied
looper
InputPython!docker run -d --name looper ubuntu:loopCopied
b9d9f370cc0de7569eb09d0a85cd67e8ea6babc0754a517ccba5c5057f5cc50e
Se agora vemos os processos que estão sendo executados dentro do contêiner
InputPython!docker exec looper ps -efCopied
UID PID PPID C STIME TTY TIME CMDroot 1 0 0 14:05 ? 00:00:00 /bin/sh -c /loop.shroot 7 1 93 14:05 ? 00:00:02 bash /loop.shroot 8 0 0 14:05 ? 00:00:00 ps -ef
Na realidade, o processo principal, o 1, não é /loop.sh, mas sim /bin/sh -c /loop.sh, ou seja, é um processo filho do shell. Portanto, quando chegava o sinal SIGTERM, ele chegava ao shell, mas este não o enviava aos seus processos filhos, por isso não chegava a loop.sh
Para que isso não aconteça, é preciso alterar o Dockerfile para o seguinte
*Dockerfile*:
FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD ["/loop.sh"] # antes era CMD /loop.shEsta forma é chamada exec form, enquanto a anterior é chamada shell form, de modo que, na forma anterior, o processo é executado como filho do shell, enquanto na forma exec form é executado o processo que lhe indicarmos. Assim, apagamos o contêiner, recompilamos e voltamos a executar o contêiner com a imagem
InputPython!docker rm -f looperCopied
looper
InputPython!docker build -t ubuntu:loop ./Dockerfile_loopCopied
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : COPY ["loop.sh", "/"]---> Using cache---> 89f2bbd25a88Step 3/3 : CMD ["/loop.sh"]---> Running in 6b8d92fcd57cRemoving intermediate container 6b8d92fcd57c---> 35a7bb2b1892Successfully built 35a7bb2b1892Successfully tagged ubuntu:loopUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
InputPython!docker run -d --name looper ubuntu:loopCopied
850ae70c071426850b28428ac60dcbf875c6d35d9b7cc66c17cf391a23392965
Sim, agora vejo os processos dentro do contêiner
InputPython!docker exec looper ps -efCopied
UID PID PPID C STIME TTY TIME CMDroot 1 0 88 14:14 ? 00:00:02 bash /loop.shroot 7 0 0 14:14 ? 00:00:00 ps -ef
Agora sim vejo que o processo principal, o 1, é /loop.sh
Se agora eu tentar parar o contêiner
InputPython%%time!docker stop looperCopied
looperCPU times: user 989 µs, sys: 7.55 ms, total: 8.54 msWall time: 529 ms
Vemos que demora mais. Vamos ver o código com o qual parou
InputPython!docker ps -aCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES850ae70c0714 ubuntu:loop "/loop.sh" About a minute ago Exited (0) 33 seconds ago looper
Contêineres executáveis
Se queremos um binário que funcione como um executável, no dockerfile é necessário especificar o comando em ENTRYPOINT e os parâmetros do comando em CMD, vamos ver isso
Vamos criar uma nova pasta onde guardaremos o Dockerfile
InputPython!mkdir dockerfile_pingCopied
Agora criamos um Dockerfile dentro
InputPython!touch dockerfile_ping/DockerfileCopied
Escrevemos dentro do Dockerfile o seguinte
FROM ubuntu:trusty
ENTRYPOINT [ "/bin/ping", "-c", "3" ]
CMD [ "localhost" ]Compilamos a imagem
InputPython!docker build -t ubuntu:ping ./dockerfile_pingCopied
Sending build context to Docker daemon 3.072kBStep 1/3 : FROM ubuntu:trusty---> 13b66b487594Step 2/3 : ENTRYPOINT [ "/bin/ping", "-c", "3" ]---> Using cache---> 1cebcfb542b1Step 3/3 : CMD [ "localhost" ]---> Using cache---> 04ddc3de52a2Successfully built 04ddc3de52a2Successfully tagged ubuntu:pingUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Se agora executarmos a imagem sem passar um parâmetro, o contêiner fará um ping a si mesmo
InputPython!docker run --name ping_localhost ubuntu:pingCopied
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 ms64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.058 ms64 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 2027msrtt min/avg/max/mdev = 0.041/0.051/0.058/0.007 ms
Mas se agora lhe passarmos um parâmetro, fará ping ao endereço que lhe indicarmos
InputPython!docker run --name ping_google ubuntu:ping google.comCopied
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 ms64 bytes from waw02s06-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=111 time=6.80 ms64 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 2002msrtt min/avg/max/mdev = 3.930/5.886/6.920/1.383 ms
Apagamos os contêineres
InputPython!docker rm ping_localhost ping_googleCopied
ping_localhostping_google
O contexto de build
Vamos criar uma pasta chamada dockerfile_contexto
InputPython!mkdir dokerfile_contextoCopied
Agora criamos nela dois arquivos: um test.txt e o Dockerfile
InputPython!touch dokerfile_contexto/Dockerfile dokerfile_contexto/text.txtCopied
Modificamos o Dockerfile e colocamos o seguinte
FROM ubuntu:trusty
COPY [".", "/"]Isto que vai fazer é copiar para dentro da imagem tudo o que estiver na pasta em que se encontra o Dockerfile. Compilamos a imagem
InputPython!docker build -t ubuntu:contexto ./dokerfile_contextoCopied
Sending build context to Docker daemon 2.56kBStep 1/2 : FROM ubuntu:trusty---> 13b66b487594Step 2/2 : COPY [".", "/"]---> 3ab79fdce389Successfully built 3ab79fdce389Successfully tagged ubuntu:contextoUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Vamos ver o que há dentro do contêiner
InputPython!docker run --name ls ubuntu:contexto lsCopied
Dockerfilebinbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystext.txttmpusrvar
Como vemos, está o arquivo text.txt. Mas é possível que dentro da pasta que está no mesmo diretório que o Dockerfile haja arquivos ou pastas que não queremos que sejam copiados para a imagem, por qualquer motivo. Então, assim como no git temos o .gitignore, no docker temos o .dockerignore, onde colocamos os arquivos ou pastas que não queremos que sejam considerados no momento da compilação
Então criamos um arquivo .dockerignore
InputPython!touch dokerfile_contexto/.dockerignoreCopied
E, dentro, adicionamos o text.txt e, de passagem, o Dockerfile, que não precisamos dentro da imagem
*.dockerignore*:
```Dockerfiletext.txt Dockerfiletext.txt ```Apagamos o contêiner que havíamos criado, voltamos a compilar e vemos o que há dentro do contêiner
InputPython!docker rm lsCopied
ls
InputPython!docker build -t ubuntu:contexto ./dokerfile_contextoCopied
Sending build context to Docker daemon 3.072kBStep 1/2 : FROM ubuntu:trusty---> 13b66b487594Step 2/2 : COPY [".", "/"]---> 7a6689546da4Successfully built 7a6689546da4Successfully tagged ubuntu:contextoUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
InputPython!docker run --name ls ubuntu:contexto lsCopied
binbootdevetchomeliblib64mediamntoptprocrootrunsbinsrvsystmpusrvar
Vemos que agora não estão nem Dockerfile nem text.txt. Apagamos o contêiner
InputPython!docker rm lsCopied
ls
Build multi-stage
No final de um desenvolvimento, não queremos que todo o código esteja na imagem que será enviada para produção.
Podemos dividir o dockerfile em dois, por exemplo, o developer.Dockerfile e o production.Dockerfile, onde no desenvolvimento haverá mais coisas do que no de produção. Na hora de compilá-los, por meio da opção -f escolhemos o dockerfile que queremos usar
docker build -t <tag> -f developer.Dockerfiledocker build -t <tag> -f production.DockerfileMas, para não ter que criar dois arquivos Dockerfile, o Docker criou os multi-stage builds. Com um único Dockerfile, vamos resolver o problema
Criamos a pasta onde vamos guardar o Dockerfile
InputPython!mkdir docker_multi_stageCopied
E dentro criamos o arquivo Dockerfile
InputPython!cd docker_multi_stage && touch DockerfileCopied
Editamos o arquivo, inserindo o seguinte
# Etapa 1: Gerar o executável com Python baseado em Alpine
FROM python:3.9-alpine as build-stage
WORKDIR /app
# Instalar dependências para PyInstaller
RUN apk add --no-cache gcc musl-dev libc-dev
# Gerar hello.py
RUN echo 'print("Hello from Alpine!")' > hello.py
# Instalar PyInstaller
RUN pip install pyinstaller
# Usar PyInstaller para criar um executável independente
RUN pyinstaller --onefile hello.py
# Etapa 2: Executar o executável em uma imagem Alpine
FROM alpine:latest
WORKDIR /app
# Copiar o executável da etapa de build
COPY --from=build-stage /app/dist/hello .
# Comando padrão para executar o executável
CMD ["./hello"]Como pode ser visto, o Dockerfile está dividido em dois. Por um lado, trabalha-se sobre a imagem python:3.9-alpine, que se chama build-stage. E, por outro lado, trabalhamos sobre a imagem alpine:latest, que é uma imagem de Linux muito leve e é muito utilizada em produção
Nós o compilamos
InputPython!docker build -t maximofn/multistagebuild:latest ./docker_multi_stageCopied
[+] Building 0.0s (0/2) docker:default
[+] Building 0.2s (4/6) docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 722B 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/alpine:latest 0.1s=> [internal] load metadata for docker.io/library/python:3.9-alpine 0.1s...=> CACHED [stage-1 3/3] COPY --from=build-stage /app/dist/hello . 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:7fb090d1495d00e892118b6bc3c03400b63a435fd4703 0.0s=> => naming to docker.io/maximofn/multistagebuild:latest 0.0s
Se agora vemos as imagens que temos
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multistagebuild latest 7fb090d1495d 8 minutes ago 13.6MB
Vamos baixar a imagem do Python para ver quanto ela pesa
InputPython!docker pull python:3.9-alpineCopied
3.9-alpine: Pulling from library/pythona8db6415: Already existsd5e70e42: Already exists3fe96417: Already existsaa4dddbb: Already exists518be9f7: Already exists Digest: sha256:6e508b43604ff9a81907ec17405c9ad5c13664e45a5affa2206af128818c7486Status: Downloaded newer image for python:3.9-alpinedocker.io/library/python:3.9-alpine
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multistagebuild latest 7fb090d1495d 9 minutes ago 13.6MBpython 3.9-alpine 6946662f018b 9 days ago 47.8MB
Podemos ver que, mientras nossa imagem pesa apenas 13,6 MB, a do Python com a qual a aplicação foi construída pesa 47,8 MB. Portanto, podemos tirar duas conclusões: com a primeira imagem, a do Python, ele construiu a aplicação, gerou o executável e esse executável é o que usamos na segunda imagem, a do Alpine. Além disso, podemos ver que, embora a primeira imagem que usa seja a do Python, ela não é baixada no nosso sistema, já que fomos nós que tivemos que baixá-la.
Bem, agora só falta testá-lo
InputPython!docker run --rm --name multi_stage_build maximofn/multistagebuildCopied
Hello from Alpine!
Funciona!
Builds multi-arch
Suponhamos que queremos fazer uma imagem que possa ser executada em um computador e em uma Raspberry. O computador provavelmente terá um processador com arquitetura AMD64, enquanto a Raspberry tem um processador com arquitetura ARM. Portanto, não podemos criar a mesma imagem para ambos. Ou seja, quando criamos uma imagem, a criamos com um Dockerfile que costuma começar assim
DE ...Portanto, o Dockerfile da imagem do computador poderia começar assim
FROM ubuntu:latestEnquanto o da Raspberry poderia começar assim
FROM arm64v8/ubuntu:latestTendríamos que crear dos archivos Dockerfile, compilarlos y en el ordenador usar una imagen y en la Raspberry usar otra
Para evitar ter que ver a arquitetura do computador e ver qual imagem temos que usar, o Docker cria os manifest, que, como o nome indica, é um manifesto que indica em função de qual arquitetura de micro tenhamos, usa uma imagem ou outra
Então, vamos ver como fazer isso
Em primeiro lugar, criamos uma pasta onde vamos criar nossos arquivos Dockerfile
InputPython!mkdir docker_multi_archCopied
Agora criamos os dois Dockerfiles
InputPython!cd docker_multi_arch && touch Dockerfile_arm64 Dockerfile_amd64Copied
Escrevemos o Dockerfile para AMD64
InputPython!cd docker_multi_arch && echo "FROM ubuntu:20.04" >> Dockerfile_amd64 && echo "CMD echo 'Hello from amd64'" >> Dockerfile_amd64Copied
InputPython!cd docker_multi_arch && echo "FROM arm64v8/ubuntu:latest" >> Dockerfile_arm && echo "CMD echo 'Hello from ARM'" >> Dockerfile_armCopied
Agora combinamos as duas imagens
InputPython!cd docker_multi_arch && docker build -t maximofn/multiarch:arm -f Dockerfile_arm .Copied
[+] Building 0.0s (0/1) docker:default[+] Building 0.2s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.1s[+] Building 0.3s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.2s[+] Building 0.5s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s[+] Building 0.6s (2/3) docker:default=> [internal] load build definition from Dockerfile_amd64 0.1s=> => transferring dockerfile: 89B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.5s...=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile_arm 0.0s=> => transferring dockerfile: 94B 0.0s=> [internal] load metadata for docker.io/arm64v8/ubuntu:latest 1.8s=> [auth] arm64v8/ubuntu:pull token for registry-1.docker.io 0.0s=> CACHED [1/1] FROM docker.io/arm64v8/ubuntu:latest@sha256:94d12db896d0 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:a9732c1988756dc8e836fd96e5c9512e349c97ea5af46 0.0s=> => naming to docker.io/maximofn/multiarch:arm 0.0s
Vamos ver o que temos nas duas imagens compiladas
InputPython!docker image lsCopied
REPOSITORY TAG IMAGE ID CREATED SIZEmaximofn/multiarch arm a9732c198875 4 weeks ago 69.2MBmaximofn/multiarch amd64 5b612c83025f 6 weeks ago 72.8MB
Vemos que compilamos as duas imagens. Para poder criar um manifest, primeiro temos que enviar as imagens para o Docker Hub, então vamos enviá-las
InputPython!docker push maximofn/multiarch:amd64Copied
The push refers to repository [docker.io/maximofn/multiarch]82bdeb5f: Mounted from library/ubuntu amd64: digest: sha256:30e820f2a11a24ad4d8fb624ae485f7c1bcc299e8cfc72c88adce1acd0447e1d size: 529
InputPython!docker push maximofn/multiarch:armCopied
The push refers to repository [docker.io/maximofn/multiarch]
eda53374: Layer already exists arm: digest: sha256:6ec5a0752d49d3805061314147761bf25b5ff7430ce143adf34b70d4eda15fb8 size: 529
Se eu for ao meu Docker Hub, posso ver que minha imagem maximofn/multiarch tem as tags amd64 e arm
Agora vamos criar o manifest com base nessas duas imagens
InputPython!docker manifest create maximofn/multiarch:latest maximofn/multiarch:amd64 maximofn/multiarch:armCopied
Created manifest list docker.io/maximofn/multiarch:latest
Uma vez criado, temos que indicar as arquiteturas das CPUs às quais cada uma corresponde
InputPython!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:amd64 --os linux --arch amd64Copied
InputPython!docker manifest annotate maximofn/multiarch:latest maximofn/multiarch:arm64 --os linux --arch arm64Copied
manifest for image maximofn/multiarch:arm64 does not exist in maximofn/multiarch:latest
Uma vez criado e anotado, podemos enviar o manifest para o Docker Hub
InputPython!docker manifest push maximofn/multiarch:latestCopied
sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Se agora eu voltar a olhar para as tags que a minha imagem maximofn/multiarch tem, também vejo a de latest
Agora, tanto se eu quiser usar minha imagem desde uma máquina com CPU AMD64 ou CPU ARM ao fazer FROM maximofn/multiarch:latest, o docker verificará a arquitetura da CPU e baixará a tag amd64 ou a tag arm. Vamos ver isso, se a partir do meu computador eu executo a imagem obtenho
InputPython!docker run maximofn/multiarch:latestCopied
Unable to find image 'maximofn/multiarch:latest' locally
latest: Pulling from maximofn/multiarchDigest: sha256:7cef0de10f7fa2b3b0dca0fbf398d1f48af17a0bbc5b9beca701d7c427c9fd84Status: Downloaded newer image for maximofn/multiarch:latestHello from amd64
Como não a tem, faz o download dela
Se agora me conecto por SSH a um Raspberry Pi e testo o mesmo, obtenho
raspberry@raspberrypi:~ $ docker run maximofn/multiarch:latest
Não foi possível encontrar a imagem 'maximofn/multiarch:latest' localmente
latest: Baixando de maximofn/multiarch
Digest: sha256:1ea28e9a04867fe0e0d8b0efa455ce8e4e29e7d9fd4531412b75dbd0325e9304
Status: Imagem mais recente baixada para maximofn/multiarch:latest
Olá do ARMAparece Hello from ARM porque a Raspberry tem um micro com arquitetura ARM
Como se pode ver, cada máquina baixou a imagem de que precisava
Escrita correta de Dockerfiles avançado
Já vimos a maneira de escrever corretamente Dockerfiles, mas há mais uma coisa que podemos fazer agora que conhecemos o multi-stage build: criar um contêiner para gerar o executável e outro menor para executá-lo
Chegamos à conclusão de que um bom Dockerfile poderia ser este
FROM python:3.9.18-alpine
WORKDIR /sourceCode/sourceApp
COPY ./sourceCode/sourceApp .
CMD ["python3", "app.py"]Vamos criar agora um executável em um contêiner builder e, em outro menor, vamos executá-lo
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"]Criamos o código Python no caminho necessário
InputPython!mkdir multistagebuild/sourceCode!mkdir multistagebuild/sourceCode/sourceApp!touch multistagebuild/sourceCode/sourceApp/app.py!echo 'print("Hello from Alpine!")' > multistagebuild/sourceCode/sourceApp/app.pyCopied
Agora compilando a imagem
InputPython!docker build -t maximofn/multistagebuild:alpine-3.18.3 ./multistagebuildCopied
[+] Building 0.0s (0/0) docker:default[+] Building 0.0s (0/1) docker:default[+] Building 0.2s (3/5) docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 357B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/alpine:3.18.3 0.1s=> [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.1s=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s[+] Building 0.3s (3/5) docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 357B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/alpine:3.18.3 0.2s=> [internal] load metadata for docker.io/library/python:3.9.18-alpine 0.2s=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s[+] Building 0.5s (4/6) docker:default=> [internal] load build definition from Dockerfile 0.1s=> => transferring dockerfile: 357B 0.0s=> [internal] load .dockerignore 0.1s=> => transferring context: 2B 0.0s=> [internal] load metadata for docker.io/library/alpine:3.18.3 0.4s...=> exporting to image 0.1s=> => exporting layers 0.1s=> => writing image sha256:8a22819145c6fee17e138e818610ccf46d7e13c786825 0.0s=> => naming to docker.io/maximofn/multistagebuild:alpine-3.18.3 0.0s
Nós a executamos
InputPython!docker run --rm --name multi_stage_build maximofn/multistagebuild:alpine-3.18.3Copied
Hello from Alpine!
A imagem maximofn/multistagebuild:alpine-3.18.3 pesa apenas 13,6 MB
Diferença entre RUN, CMD e ENTRYPOINT
EXECUTAR
O comando RUN é o mais simples, simplesmente executa um comando no momento da compilação da imagem. Por exemplo, se quisermos instalar um pacote na imagem, fazemos isso por meio de RUN.
Portanto, é importante: RUN é executado no momento da compilação da imagem, não quando o contêiner é executado
CMD
O comando CMD é o comando que é executado quando o contêiner é executado. Por exemplo, se quisermos que o contêiner execute um comando quando for executado, fazemos isso por meio de CMD. Por exemplo, se tivermos uma aplicação em Python em um contêiner, com CMD podemos indicar que, quando o contêiner for executado, execute a aplicação em Python.
Dessa maneira, quando o contêiner for iniciado, a aplicação de Python será executada. Ou seja, se fizermos docker run <image> a aplicação de Python será executada. Mas CMD nos permite sobrescrever o comando que é executado quando o contêiner é iniciado; por exemplo, se fizermos docker run <image> bash bash será executado em vez da aplicação de Python.
PONTO DE ENTRADA
O comando ENTRYPOINT é semelhante ao comando CMD, mas com uma diferença: e é que ENTRYPOINT não foi pensado para ser sobrescrito. Ou seja, se tivermos uma aplicação Python num contentor, com ENTRYPOINT podemos indicar que, quando o contentor for executado, execute a aplicação Python. Mas se fizermos docker run <image> bash, a aplicação Python será executada, e não bash.
Um uso muito comum de ENTRYPOINT é quando queremos que o contêiner seja um executável; por exemplo, se quisermos que o contêiner seja um executável de uma versão do Python que não temos no nosso host, porque, por exemplo, queremos testar a nova versão do Python que foi lançada, podemos fazer
FROM python:3.9.18-alpine
ENTRYPOINT ["python3"]Dessa forma, quando o contêiner for iniciado, o Python será executado. Ou seja, se fizermos docker run <image>, o Python será executado. Mas ENTRYPOINT nos permite sobrescrever o comando que é executado quando o contêiner é iniciado; por exemplo, se fizermos docker run <image> myapp.py, será executado python3 myapp.py dentro do contêiner. Assim, podemos testar nossa aplicação Python na nova versão do Python
Alterações em um contêiner
Com docker diff podemos ver as diferenças que existem entre o contêiner e a imagem, o que é o mesmo que a diferença no contêiner desde que foi criado até agora
Vamos executar um contêiner e, dentro dele, criar um arquivo
InputPython!docker run --rm -it --name ubuntu-20.04 ubuntu:20.04 bashCopied
root@895a19aef124:/# touch file.txt
Agora podemos ver a diferença
InputPython!docker diff ubuntu-20.04Copied
C /rootA /root/.bash_historyA /file.txt
A significa que foi adicionado, C significa que foi alterado e D significa que foi apagado
Docker no Docker
Suponhamos que temos contêineres que precisam iniciar ou desligar outros contêineres. Isso é alcançado da seguinte forma
Como no Linux tudo é um arquivo e o host se comunica com o Docker por meio de um socket. Portanto, para o Linux, esse socket é um arquivo. Então, se montarmos esse socket como um arquivo no contêiner, ele poderá se comunicar com o Docker
Primeiro, vamos montar um contêiner com Ubuntu
InputPython!docker run -d --name ubuntu ubuntu:latest tail -f /dev/nullCopied
144091e4a3325c9068064ff438f8865b40f944af5ce649c7156ca55a3453e423
Vamos montar o contêiner que vai poder falar com o Docker montando a pasta /var/run/docker.sock
$ docker run -it --rm --name main -v /var/run/docker.sock:/var/run/docker.sock docker:19.03.12
/ #Entramos dentro de um contêiner e, se dentro dele executarmos docker ps
# docker ps
CONTAINER ID IMAGEM COMANDO CRIADO STATUS PORTAS NOMES9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 segundos atrás Em execução há 2 segundos main
144091e4a332 ubuntu:latest "tail -f /dev/null" 19 segundos atrás Ativo há 18 segundos ubuntuComo podemos ver, dentro do Docker podemos ver os contêineres do host
Podemos executar um novo contêiner
# docker run -d --name ubuntu_from_main ubuntu:latest tail -f /dev/null
362654a72bb0fb047c13968707a6f16b87fed7ce051eb5c1a146b15828589a1a
/ #E se voltarmos a ver os contêineres
# docker ps
CONTAINER ID IMAGE COMANDO CRIADO STATUS PORTAS NOMES362654a72bb0 ubuntu:latest "tail -f /dev/null" 3 segundos atrás Em execução há 3 segundos ubuntu_from_main
9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" Há cerca de um minuto Ativo há cerca de um minuto main
144091e4a332 ubuntu:latest "tail -f /dev/null" 2 minutos atrás Em execução há cerca de um minuto ubuntuMas se agora executarmos um novo terminal do host, veremos o contêiner criado de dentro do contêiner
InputPython!docker psCopied
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES362654a72bb0 ubuntu:latest "tail -f /dev/null" About a minute ago Up About a minute ubuntu_from_main9afb778d6c20 docker:19.03.12 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes main144091e4a332 ubuntu:latest "tail -f /dev/null" 3 minutes ago Up 3 minutes ubuntu
Tudo o que fizermos a partir do contêiner main será refletido no host
Isso tem a vantagem de que podemos instalar programas em um contêiner que tem acesso ao host para não termos que instalá-los no host. Por exemplo dive é uma ferramenta para explorar contêineres, mas se você não quiser instalá-la no host, pode instalá-la em um contêiner com acesso ao host, assim, a partir desse contêiner main, você pode explorar o restante dos contêineres sem ter que instalá-la no host