Imagens Docker
DockerHub
As imagens do Docker vêm do Docker Hub por padrão. O Docker Hub funciona como se fosse um GitHub de imagens. É o repositório de imagens oficial do Docker, que tem como função principal armazenar imagens.
Existe só o Docker Hub? Não, existem vários outros registries:
- DTR Docker Trusted Registry (Cai na prova)
- ECR da AWS
- ACR da Azure
- GitHub Package Registry
- GitLab Container Registry
- GAR Google Artifact Registry
- Harbor Container Registry
- Sonatype Nexus
- JFrog Artifactory
O Docker Hub somente armazena imagens? Não
- Hospeda as imagens
- Autentica usuário
- Automatiza o processo de construção de imagens, através de triggers e webhooks
- Integração com outros repositórios: GitHub, Bitbucket, GitLab, etc.
Para subir uma imagem sua para o Docker Hub é necessário criar uma conta. Para procurar uma imagem não precisa estar logado.
Exemplo de uma pesquisa da imagem do ubuntu https://hub.docker.com/search?q=ubuntu
Para logar no docker hub
vagrant@master:~$ docker login -u davidpuziol # logando
Password:
WARNING! Your password will be stored unencrypted in /home/vagrant/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
vagrant@master:~$
Esse warning quer dizer que a senha estará descriptografada no diretório apontado. Se você fizer um decode base64 no hash dentro de ~/.docker/config.json você verá a senha real de fato.
# Decodificação de token base64
vagrant@master:~$ echo "token" | base64 --decode
davidpuziol:senha
Se alguém logar na mesma máquina que você executou o login do seu Docker Hub esta pessoa deve conseguir suas credenciais, por isso é necessário sempre dar um logout que o config.json irá zerar.
vagrant@master:~$ docker logout # deslogando
Removing login credentials for https://index.docker.io/v1/
vagrant@master:~$ cat ~/.docker/config.json
{
"auths": {}
}
Image
O que é uma imagem? É um pacote executável. É um programa, só que tudo que esse programa precisa para funcionar está lá dentro. Não é só o programa, mas também todas suas dependências. Tem bibliotecas, variáveis de ambiente, arquivos de configuração, código do programa que será executado, etc
Um detalhe sobre image é que hoje existe um padrão a ser respeitado para que todas as imagens funcionem agnóstica a plataforma de container que vc estiver usando. Esse movimento é feito pela OCI (Open Container Iniciative) opencontainers.org que tem governancia pela própria Linux Foundations. Basicamente são as regras de como uma imagem deve definida para exeucão ou criação. O docker fez a doação dos seus manifestos para execução e formato de containers, para a oci e outros projetos começaram a nascer.
As imagens trabalham em cima de camadas. Somente a camada no topo da pilha pode ser escrita e as demais abaixo são somente leitura. Por isso vc pode utilizar uma imagem como base e criar outra. Esta é uma forma de reutilização de imagens.
Os containers utilizam a imagem base e somente no seu bloco Read Write ele trabalha com o diff em memória. Dessa forma não é necessário ter uma cópia da imagem em cada um dos containers, mas compartilhar a imagem, economizando um recurso precioso de espaço em disco. Essa tecnologia é chamada de COW (Copy On Write. Por isso essa camada é somente Read.
Observando essa imagem podemos entender que todos os container juntos ocupam a imagem_base + diff container1 + diff container 2 + diff container 3 + diff container n. Se fosse em máquinas virtuais cada container além do seu diff teria uma imagem extra em cada uma das vms.
Um grupo de camadas read only é o que chamados de imagem.
history e inspect
Esse comando é utilizado para ver as camadas de uma imagem.
}vagrant@master:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
debian latest 4eacea30377a 3 weeks ago 124MB
vagrant@master:~$ docker image history debian # conferindo o history
IMAGE CREATED CREATED BY SIZE COMMENT
4eacea30377a 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:dd3d4b31d7f1d4062… 124MB
vagrant@master:~$
Para inspectionar uma imagem
agrant@master:~$ docker image inspect debian:latest # inspecionando a imagem
[
{
"Id": "sha256:4eacea30377a698ef8fbec99b6caf01cb150151cbedc8e0b1c3d22f134206f1a",
"RepoTags": [
"debian:latest"
],
"RepoDigests": [
"debian@sha256:3f1d6c17773a45c97bd8f158d665c9709d7b29ed7917ac934086ad96f92e4510"
],
"Parent": "",
"Comment": "",
"Created": "2022-05-28T01:20:12.59253565Z",
"Container": "86b72732f393d3e9fa438dd5261a9c9e1903338d14171c687b3f3e7b1ede253f",
"ContainerConfig": {
"Hostname": "86b72732f393",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"bash\"]"
],
"Image": "sha256:d31d2b49944f50ccb549e957eb19f6115d9f810044fa211c6ae20f3583a8e391",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "20.10.12",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"bash"
],
"Image": "sha256:d31d2b49944f50ccb549e957eb19f6115d9f810044fa211c6ae20f3583a8e391",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 124005260,
"VirtualSize": 124005260,
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/f9386627907896f35bffaae2876719e9f7d303361b00702e6fdf75aeb4e9807b/merged",
"UpperDir": "/var/lib/docker/overlay2/f9386627907896f35bffaae2876719e9f7d303361b00702e6fdf75aeb4e9807b/diff",
"WorkDir": "/var/lib/docker/overlay2/f9386627907896f35bffaae2876719e9f7d303361b00702e6fdf75aeb4e9807b/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [ #aqui podemos observar as camadas
"sha256:e7597c345c2eb11bce09b055d7c167c526077d7c65f69a7f3c6150ffe3f557ea"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
vagrant@master:~$
Criando uma imagem a partir de container em execução
vamos subir uma imagem do debian e la dentro instalar o nginx. Depois de tudo instalado vamos fazer um commit para criar uma imagem em cima do que instalamos.
vagrant@master:~$ docker container run -dit --name server-debian debian # rodando o container
vagrant@master:~$ docker container exec server-debian apt-get update # rodando um comando dentro do container
(removido para melhorar a leitura)
docker container exec server-debian apt-get install nginx -y # rodando outro comando no container
(removido para melhorar a leitura)
vagrant@master:~$ docker container commit server-debian webserver-nginx # commitando o container como esta
vagrant@master:~$ docker image ls # conferindo se a imagem de fato agora esta disponível
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-nginx latest 8e3f55a1d009 55 seconds ago 211MB
debian latest 4eacea30377a 3 weeks ago 124MB
vagrant@master:~$
Isso é considerado uma gambiarra. Quando criamos uma imagem a partir de um container em execução, temos muito lixo dentro dessa imagem, como logs por exemplo, arquivos temporários, etc. Isso não é o certo.
Vamos fazer uma análise de uma imagem pura do nginx.
vagrant@master:~$ docker image pull nginx # baixando a imagem do nginx direto do dockerhub
Using default tag: latest
latest: Pulling from library/nginx
42c077c10790: Pull complete
62c70f376f6a: Pull complete
915cc9bd79c2: Pull complete
75a963e94de0: Pull complete
7b1fab684d70: Pull complete
db24d06d5af4: Pull complete
Digest: sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
vagrant@master:~$ docker image ls # conferindo
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-nginx latest 8e3f55a1d009 5 minutes ago 211MB
nginx latest 0e901e68141f 3 weeks ago 142MB
debian latest 4eacea30377a 3 weeks ago 124MB
Veja que a imagem do nginx oficial tem 142 mb contra 211 da que criamos. Se vc não passa a versão ele baixa a ultima latest
save e load
Vamos salvar a imagem que criamos em um arquivo
vagrant@master:~$ docker container ls --all # listando todos os container existentes
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ad7131235d91 debian "bash" 3 hours ago Exited (255) About a minute ago server-debian
vagrant@master:~$ docker image ls # listando as imagens do sistema
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-nginx latest 8e3f55a1d009 3 hours ago 211MB
nginx latest 0e901e68141f 3 weeks ago 142MB
debian latest 4eacea30377a 3 weeks ago 124MB
vagrant@master:~$ docker image save webserver-nginx -o webserver-ngin.tar # salvando a imagem em um arquivo tar
vagrant@master:~$ ls -lha # conferindo para ver se salvou
total 207M
drwxr-xr-x 5 vagrant vagrant 4.0K Jun 20 07:20 .
drwxr-xr-x 4 root root 4.0K Jun 19 18:30 ..
-rw------- 1 vagrant vagrant 5.0K Jun 20 05:07 .bash_history
-rw-r--r-- 1 vagrant vagrant 220 Jun 15 21:53 .bash_logout
-rw-r--r-- 1 vagrant vagrant 3.7K Jun 15 21:53 .bashrc
drwx------ 2 vagrant vagrant 4.0K Jun 19 18:30 .cache
drwx------ 2 vagrant vagrant 4.0K Jun 20 03:20 .docker
-rw-r--r-- 1 vagrant vagrant 807 Jun 15 21:53 .profile
drwx------ 2 vagrant vagrant 4.0K Jun 19 18:30 .ssh
-rw-rw-r-- 1 vagrant vagrant 0 Jun 20 01:26 5000
-rw-rw-r-- 1 vagrant vagrant 20K Jun 19 18:35 get-docker.sh
-rw------- 1 vagrant vagrant 207M Jun 20 07:20 webserver-ngin.tar
vagrant@master:~$ docker container rm -f server-debian
server-debian # removendo o container forçadamente
vagrant@master:~$ docker container ls --all # Conferindo
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
vagrant@master:~$ docker image rm webserver-nginx
Untagged: webserver-nginx:latest # removendo a imagem
Deleted: sha256:8e3f55a1d0097d1664ee33c2b418ecafa41ee9abc94fc287a0fc95ac2a982f9f
Deleted: sha256:fd023343dfcf8099a714adb7c80405f489a47c9ccfb2c7d91a9b285d78314c58
vagrant@master:~$ docker image ls # conferindo
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 0e901e68141f 3 weeks ago 142MB
debian latest 4eacea30377a 3 weeks ago 124MB
vagrant@master:~$ docker image load -i webserver-ngin.tar # carregando a imagem
2cafde399cfb: Loading layer [==================================================>] 87.71MB/87.71MB
Loaded image: webserver-nginx:latest
vagrant@master:~$ docker image ls # conferindo
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-nginx latest 8e3f55a1d009 3 hours ago 211MB
nginx latest 0e901e68141f 3 weeks ago 142MB
debian latest 4eacea30377a 3 weeks ago 124MB
vagrant@master:~$
Durante o carregamento da imagem ele carregou 87MB, mas aparece que webserver-nginx tem 211, pq? Uma das camadas desse arquivo era o debian que estava disponível com 124 MB. 124MB do debian + 87MB do save = 211MB. Ele só carregou a parte que não tinha.
Se fizermos o delete tanto do debian e do webserver e carregar novamente. Veja que agora ele vai carregar duas imagens. Podemos constatar que ele sempre carrega as camadas e não as imagens completamente. Logo após fiz um pull na imagem do debian e eles mostra que já existe.
vagrant@master:~$ docker image rm $(docker image ls -q) #removendo todas as imagens
vagrant@master:~$ docker image load -i webserver-ngin.tar # carregando a imagem salva
e7597c345c2e: Loading layer [==================================================>] 129.2MB/129.2MB
2cafde399cfb: Loading layer [==================================================>] 87.71MB/87.71MB
Loaded image: webserver-nginx:latest
vagrant@master:~$ docker image ls # listando as imagens para conferir
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver-nginx latest 8e3f55a1d009 4 hours ago 211MB
vagrant@master:~$ docker pull debian # puxando a imagem do debian
Using default tag: latest
latest: Pulling from library/debian
e756f3fdd6a3: Already exists 3 # veja que já existe
Digest: sha256:3f1d6c17773a45c97bd8f158d665c9709d7b29ed7917ac934086ad96f92e4510
Status: Downloaded newer image for debian:latest
docker.io/library/debian:latest
Dockerfile
https://docs.docker.com/engine/reference/builder/
Os arquivos que vamos utilizar estáo dentro de dockerfiles
O Dockerfile é a maneira correta de se criar uma imagem. Através do dockerfile podemos criar uma ou várias imagens.
O Dockerfile precisa ser escrito desta maneira Dockerfile com o D maiusculo. O Dockerfile é uma sequência de comandos que serão executados para gerar a imagem.
Alguns comandos excenciais:
- FROM - Qual é a imagem base
- COPY - Copia arquivos arquivos ou diretórios de origem local para a imagem do container
- RUN - Executa um comando dentro do container
- ADD - Quase igual o copy mas aceita origien não local como url e alterar permissões
- EXPOSE - Expões uma porta para o daemon. Avisa para o docker qual a porta de rede que ele vai utilizar
- ENTRYPOINT - É o que mantém o container vivo
- CMD - Argumentos com o entrypoint.
Para entender o dockerfile vamos começar a montar alguns.
Diferenca entre ENTRYPOINT e CMD
Vamos utilizar o dockerfile
O ENTRYPOINT é o programa que mantém o container vivo. O CMD são os argumentos que passamos para esse entrypoint.
Para entender melhor essa diferença podemos criar um arquivo Dockerfile e mostrar a diferença.
Vamos criar uma pasta pra gente trabalhar dentro do master.
mkdir -p dockerfiles/echo-container
cd dockerfiles/echo-container
cat << EOF > Dockerfile
FROM alpine
ENTRYPOINT [ "echo" ]
CMD ["--help"]
EOF
Observe que estou dando o comando dentro da pasta ontem tem o Dockerfile que estamos buildando, por isso usando "."
~vagrant@master:~/dockerfiles/echo-container$ docker image build -t echo-container . # buildando uma imagem
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM alpine # primeira camada
latest: Pulling from library/alpine
2408cc74d12b: Pull complete
Digest: sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c
Status: Downloaded newer image for alpine:latest
---> e66264b98777
Step 2/3 : ENTRYPOINT [ "echo" ] # segunda camada
---> Running in 7100d2e1505b
Removing intermediate container 7100d2e1505b
---> 0c771e92b47d
Step 3/3 : CMD ["--help"] # terceira camada
---> Running in 5ea99678316a
Removing intermediate container 5ea99678316a
---> 6da75d1ad4bb
Successfully built 6da75d1ad4bb
Successfully tagged echo-container:latest
Se usarmos a imagem vamos entender a diferença entre o entrypoint e cmd. Se não passamors nada o cmd já foi definido como --help. Se passarmos algum parametro ele escreverá o que passamos, pois o cmd pode sofrer overwrite por padrão.
vagrant@master:~/dockerfiles/echo-container$ docker container run echo-container
--help
vagrant@master:~/dockerfiles/echo-container$ docker container run echo-container estou aprendendo docker
estou aprendendo docker
Agora vamos melhorar o nosso webserve nginx vá para a pasta dockerfiles e crie uma nova pasta e entre dentro dela.
vagrant@master:~/dockerfiles/echo-container$ cd ..
vagrant@master:~/dockerfiles$ mkdir webserver
vagrant@master:~/dockerfiles$ cd webserver
cat << EOF > Dockerfile
FROM debian
RUN apt-get update; \
apt-get install git apache2 -yq
EXPOSE 80
ENTRYPOINT ["apachectl"]
CMD ["-D", "FOREGROUND"]
EOF
vagrant@master:~/dockerfiles/webserver$ docker image build -t webserver
...
...
...
Step 5/5 : CMD ["-D", "FOREGROUND"]
---> Running in 8d324204a768
Removing intermediate container 8d324204a768
---> 29c57f4397e7
Successfully built 29c57f4397e7
Successfully tagged webserver:latest
Foram criadas 5 camadas. Se dividissemos o RUN em
RUN apt-get update
RUN apt-get install git apache2 -yq
ao invés de colocar tudo numa linha iria criar 6 camadas. Uma boa prática é manter o menor número de camadas.
Push da imagem para o registry
vc precisa taggear uma imagem para o seu usuário para poder subir no dockerhub. Se não passarmos uma versão ele irá adotar a latest, mas vamos passar v1.
vagrant@master:~/dockerfiles/webserver$ docker login
Username: davidpuziol
Password:
Login Succeeded
vagrant@master:~/dockerfiles/webserver$ docker image tag echo-container:latest davidpuziol/echo-container:v1
vagrant@master:~/dockerfiles/webserver$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver latest 29c57f4397e7 9 minutes ago 300MB
echo-container latest 6da75d1ad4bb 19 minutes ago 5.53MB
davidpuziol/echo-container v1 6da75d1ad4bb 19 minutes ago 5.53MB
webserver-nginx latest 8e3f55a1d009 10 hours ago 211MB
debian latest 4eacea30377a 3 weeks ago 124MB
alpine latest e66264b98777 3 weeks ago 5.53MB
vagrant@master:~/dockerfiles/webserver$ docker image push davidpuziol/echo-container:v1
The push refers to repository [docker.io/davidpuziol/echo-container]
24302eb7d908: Mounted from library/alpine
v1: digest: sha256:880b12fea1dc826477b5cb0e6baab84c5c8927bb4dbb79ae00fcf9cfc5b7ede1 size: 528
vagrant@master:~/dockerfiles/webserver$
Podemos agora fazer um pull direto nessa imagem de qualquer lugar. Vamos conferir e ver que ela esta lá já no dockerhub.
Dockerfile context
Quando estamos fazenod um build da imagem, o . (ponto) representa o local onde esta o contexto do dockerfile. É o diretório que será enviado ao container. Quando passamos o contexto sem expecificar onde esta o dockerfile ele entende que o arquivo Dockerfile esta no mesmo contexto.
Para criar a imagem como vimos anteriormente é necessário o comando
docker image build -t nome:tag .
Podemos passar o contexto e expecificar o dockerfile com
docker image build -t nome:tag -f dockerfilepath contextpath
Devemos ter cuidado com qual contexto vamos passar para o build pois podemos passar arquivos que poluem e aumentam o contexto. Vamos ver a diferença.
Vamos textar com o nosso echo container na pasta do nosso próprio Dockerfile. Foi utilizado o time na frente do comando para pegar o tempo de processamento desse build.
vagrant@master:~/dockerfiles/echo-container$ tree
.
└── Dockerfile
0 directories, 1 file
vagrant@master:~/dockerfiles/echo-container$ time docker image build -t teste .
# Veja o tamanho 2.048kb
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM alpine
---> e66264b98777
Step 2/3 : ENTRYPOINT [ "echo" ]
---> Running in 58d981554e13
Removing intermediate container 58d981554e13
---> 92912c05d84c
Step 3/3 : CMD ["--help"]
---> Running in 4de69295ddb1
Removing intermediate container 4de69295ddb1
---> 64849f04738f
Successfully built 64849f04738f
Successfully tagged teste:latest
# Veja o tempo de execução
real 0m0.369s
user 0m0.045s
sys 0m0.023s
Agora vamos criar um arquivolixo com um pouco de caracteres dentro e subir o diretório usar o contexto com um diretório acima. Tamanho
vagrant@master:~/dockerfiles/echo-container$ cd ..
vagrant@master:~/dockerfiles$ vim arquivolixo
vagrant@master:~/dockerfiles$ tree
.
├── arquivolixo
└── echo-container
└── Dockerfile
1 directory, 2 files
vagrant@master:~/dockerfiles$ time docker image build -t teste:v2 -f echo-container/Dockerfile .
# Veja como o tamanho já aumentou
Sending build context to Docker daemon 3.584kB
Step 1/3 : FROM alpine
---> e66264b98777
Step 2/3 : ENTRYPOINT [ "echo" ]
---> Using cache
---> 92912c05d84c
Step 3/3 : CMD ["--help"]
---> Using cache
---> 64849f04738f
Successfully built 64849f04738f
Successfully tagged teste:v2
# e o tempo também
real 0m0.107s
user 0m0.020s
sys 0m0.020s
Agora vamos copiar mais lixo ainda, um monte de log do /var/log para a pasta afim de aumentar bastante.
vagrant@master:~/dockerfiles$ sudo cp -r /var/log/ .
vagrant@master:~/dockerfiles$ tree
.
├── arquivolixo
├── echo-container
│ └── Dockerfile
└── log
├── apt
│ ├── eipp.log.xz
│ ├── history.log
│ └── term.log
├── auth.log
├── btmp
├── cloud-init-output.log
├── cloud-init.log
├── dist-upgrade
├── dmesg
├── dpkg.log
├── journal
│ └── 925f882e39344f0db2ae9ae1bd831c5e
│ ├── system.journal
│ └── user-1000.journal
├── kern.log
├── landscape
│ └── sysinfo.log
├── lastlog
├── private
├── syslog
├── unattended-upgrades
│ └── unattended-upgrades-shutdown.log
└── wtmp
vagrant@master:~/dockerfiles$ sudo chown vagrant:vagrant * -R # Precisamos mudar a permissao senão o build não consegue ler os arquivos.
vagrant@master:~/dockerfiles$ time docker image build -t teste:v3 -f echo-container/Dockerfile .
# Olha como já aumentou para 17.62MB
Sending build context to Docker daemon 17.62MB
Step 1/3 : FROM alpine
---> e66264b98777
Step 2/3 : ENTRYPOINT [ "echo" ]
---> Using cache
---> 92912c05d84c
Step 3/3 : CMD ["--help"]
---> Using cache
---> 64849f04738f
Successfully built 64849f04738f
Successfully tagged teste:v3
# O tempo também
real 0m0.237s
user 0m0.046s
sys 0m0.017s
Agora vamos conferir o tamanho das imagens
9 directories, 19 files
vagrant@master:~/dockerfiles$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
teste latest 64849f04738f 4 minutes ago 5.53MB
teste v2 64849f04738f 4 minutes ago 5.53MB
teste v3 64849f04738f 4 minutes ago 5.53MB
alpine latest e66264b98777 4 weeks ago 5.53MB
Apesar do contexto ser passado para a criação da imagem, ele não utilizou o contexto, logo todas as imagens permaneceram do mesmo tamanho, mas o tempo de build foi piorando para processar os arquivos do contexto.
Vou fazer o download da iso do ubuntu e passar o contexto na home do usuário e vemos medir o tempo. Vamos gerar a v4.
# Lembrando que estamos na /home/vagrant ou seja, a home do usuário
vagrant@master:~$ tree
├── dockerfiles
│ ├── arquivolixo
│ ├── echo-container
│ │ └── Dockerfile
│ └── log
│ ├── apt
│ │ ├── eipp.log.xz
│ │ ├── history.log
│ │ └── term.log
│ ├── auth.log
│ ├── btmp
│ ├── cloud-init-output.log
│ ├── cloud-init.log
│ ├── dist-upgrade
│ ├── dmesg
│ ├── dpkg.log
│ ├── journal
│ │ └── 925f882e39344f0db2ae9ae1bd831c5e
│ │ ├── system.journal
│ │ └── user-1000.journal
│ ├── kern.log
│ ├── landscape
│ │ └── sysinfo.log
│ ├── lastlog
│ ├── private
│ ├── syslog
│ ├── unattended-upgrades
│ │ └── unattended-upgrades-shutdown.log
│ └── wtmp
# a imagem aqui...
├── ubuntu-22.04-desktop-amd64.iso
├── wget-log
└── wget-log.1
vagrant@master:~$ time docker image build -t teste:v4 -f dockerfiles/echo-container/Dockerfile .
# Olha o quanto carregou
Sending build context to Docker daemon 3.673GB
Step 1/3 : FROM alpine
---> e66264b98777
Step 2/3 : ENTRYPOINT [ "echo" ]
---> Using cache
---> 92912c05d84c
Step 3/3 : CMD ["--help"]
---> Using cache
---> 64849f04738f
Successfully built 64849f04738f
Successfully tagged teste:v4
# olha o tempo 50 segundos
real 0m50.932s
user 0m1.878s
sys 0m5.957s
vagrant@master:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
teste v2 64849f04738f 55 minutes ago 5.53MB
teste v3 64849f04738f 55 minutes ago 5.53MB
# mas a imagem esta do mesmo tamanho
teste v4 64849f04738f 55 minutes ago 5.53MB
alpine latest e66264b98777 4 weeks ago 5.53MB
Docker file boas práticas
Uma boa prática é criar o seguinte esquema de diretórios.
vagrant@master:~/dockerfiles$ tree -a exemplo-squeme/
exemplo-squeme/
├── context
│ ├── .dockerignore
│ ├── arquivo1
│ └── arquivo2
└── image
└── Dockerfile
2 directories, 4 files
vagrant@master:~/dockerfiles$
Dentro da pasta image tem o nosso Dockerfile que pode servir tanto para ambiente de produção e desenvolvimento, mas mudará o context.
Dentro de context teremos os arquivos úteis ao nosso container e o .dockerignore que irá ignorar alguns arquivos que ali podem estar.
Aprendendo como criar as imagens
Vamos a um novo exemplo montando o mesmo esquema definido acima
Vamos copiar o a pasta log que temos para dentro do nosso context só para ter lixo junto e usarmos o git ignore
vagrant@master:~/dockerfiles$ mkdir -p exemplo1/image
vagrant@master:~/dockerfiles$ mkdir -p exemplo1/context
vagrant@master:~/dockerfiles$ echo "Aprendendo sobre images" > exemplo1/context/arquivo.txt
# colocando para ser ignorado log no .dockerignore
vagrant@master:~/dockerfiles$ echo "log" > exemplo1/context/.dockerignore
# o comando COPY vai copiar tudo do context no caso o . para dentro de /files no container e deois vamos imprimir o que esta no arquivo
vagrant@master:~/dockerfiles$ cat << EOF > exemplo1/image/Dockerfile
FROM busybox
COPY . /files
RUN cat /files/arquivo.txt
EOF
vagrant@master:~/dockerfiles$ tree -a -du -h -L 3 exemplo1
exemplo1
├── [vagrant 4.0K] context
│ └── [vagrant 4.0K] log
│ ├── [vagrant 4.0K] apt
│ ├── [vagrant 4.0K] dist-upgrade
│ ├── [vagrant 4.0K] journal
│ ├── [vagrant 4.0K] landscape
│ ├── [vagrant 4.0K] private
│ └── [vagrant 4.0K] unattended-upgrades
└── [vagrant 4.0K] image
9 directories
vagrant@master:~/dockerfiles$
Vamos rodar o container e cair pra dentro dele para verificar o que foi copiado e se o .dockerignore funcionou
vagrant@master:~/dockerfiles$ docker container run --rm -it --rm teste1:v1 sh
/ $ ls -lha
total 48K
drwxr-xr-x 1 root root 4.0K Jun 22 20:00 .
drwxr-xr-x 1 root root 4.0K Jun 22 20:00 ..
-rwxr-xr-x 1 root root 0 Jun 22 20:00 .dockerenv
drwxr-xr-x 2 root root 12.0K Jun 6 22:13 bin
drwxr-xr-x 5 root root 360 Jun 22 20:00 dev
drwxr-xr-x 1 root root 4.0K Jun 22 20:00 etc
# PASTA FILES
drwxr-xr-x 2 root root 4.0K Jun 22 19:55 files
drwxr-xr-x 2 nobody nobody 4.0K Jun 6 22:13 home
dr-xr-xr-x 190 root root 0 Jun 22 20:00 proc
drwx------ 1 root root 4.0K Jun 22 20:00 root
dr-xr-xr-x 13 root root 0 Jun 22 20:00 sys
drwxrwxrwt 2 root root 4.0K Jun 6 22:13 tmp
drwxr-xr-x 3 root root 4.0K Jun 6 22:13 usr
drwxr-xr-x 4 root root 4.0K Jun 6 22:13 var
/ # cd files/
/files $ ls -lha
total 16K
drwxr-xr-x 2 root root 4.0K Jun 22 19:55 .
drwxr-xr-x 1 root root 4.0K Jun 22 20:00 ..
-rw-rw-r-- 1 root root 38 Jun 22 19:40 .dockerignore
-rw-rw-r-- 1 root root 24 Jun 22 19:39 arquivo.txt
/files $ cat arquivo.txt
Aprendendo sobre images
Copiou tudo até o .dockerfile que não é necessário, mas o importane é que o log não esta aqui.
Velocidades no build
Quando executamos um build o docker cria um cache entre as camadas, logo quando executamos um build novo ele faz mais rapido. Se for necessário não utilizar o cache somente passar no build o parametro --no-cache.
Criei um exemplo 2 para mostrar com a seguinte imagem
FROM debian
COPY . .
RUN apt-get update; apt-get install -y wget ssh vim
ENTRYPOINT bash
Se mudarmos o arquivo de copy ele irá invalidar todo o cache dali pra frente, ou seja, vai novamente ter que fazer o apt-get update e install
# criando exemplo 2
vagrant@master:~/dockerfiles/exemplo2/image$ docker image build -t exemplo2 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM debian
---> 4eacea30377a
Step 2/4 : COPY . .
---> 6c69c907ce60
Step 3/4 : RUN apt-get update; apt-get install -y wget ssh vim
---> Running in e9f30c474256
#.... PARTE REMOVIDA MUITO GRANDE....#
..
done.
Removing intermediate container e9f30c474256
---> 9e568a952863
Step 4/4 : ENTRYPOINT bash
---> Running in dad4f2f64329
Removing intermediate container dad4f2f64329
---> 6ba55366c298
Successfully built 6ba55366c298
Successfully tagged exemplo2:latest
# Criando outra imagem exemplo3
vagrant@master:~/dockerfiles/exemplo2/image$ docker image build -t exemplo3 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM debian
---> 4eacea30377a
Step 2/4 : COPY
# veja o uso do cache que ele aproveitou do build anterior do exemplo 2 . .
---> Using cache
---> 6c69c907ce60
Step 3/4 : RUN apt-get update; apt-get install -y wget ssh vim
# mais um uso de cache
---> Using cache
---> 9e568a952863
Step 4/4 : ENTRYPOINT bash
# outro...
---> Using cache
---> 6ba55366c298
Successfully built 6ba55366c298
Successfully tagged exemplo3:latest
O segundo primeiro build demorou cerca de 20 segundos enquanto o segundo aproveitou o cache e foi instantaneo e não gerou nenhuma saída grande.
Dicas
A seguir varias dicas para melhorar a criação das imagens.
Dica 1 - A ordem importa para o cache
A ordem com que os comandos são colocado no Dockerfile que vamos montar IMPORTA. Lembrando que cada comando cria uma camada nova, logo se uma camada acima for modificada ele invalida todo o cache que foi feito para baixo.
Para o exemplo acima, se tivesse alguma mudança no copy ele irá invalidar todos os caches dos passos seguintes. O ideal seria assim:
FROM debian
RUN apt-get update
RUM apt-get install -y wget ssh vim
COPY . .
ENTRYPOINT bash
Dica 2 - Copy mais específico para limitar a quebra de cache
Uma dica seria separar o copy em vários copy gerando camadas entre eles. Arquivos que não sofrem modificações podem vir na frente. O ideal é evitar o copy, mas sabemos que não é tão fácil assim. Evite copiar qualquer coisa que tenha modificação e arquivos desnecessários, pois ele sempre quebrará o cache.
Dica 3 - Identifique as instruções que podem ser agrupadas
Cada instrução gera uma camada diferente, logo reduzir a quantidade de camadas é extremamente importante.
Se observar o uso do ponto virgula entre os comandos faz com que se o primeiro comando falhar executa o segundo. Não é boa prática, pois pode ainda dar algum tipo de problema.
FROM debian
RUN apt-get update; apt-get install -y wget ssh vim
COPY . .
ENTRYPOINT bash
faça com && que o próximo comando somente irá executar se o primeiro for bem sucedido.
FROM debian
RUN apt-get update \
&& apt-get install -y \
wget \
ssh \
vim
COPY . .
ENTRYPOINT bash
Dica 4 - Remover dependências desnecessárias
Não instale pacotes que não precisam ser instalados.
-
Exemplo1: Se fosse java, não instale o pacote jdk (desenvolvimento) mas o jre (somente o runtime).
-
Exemplo2: passe o parametro --no-install-recommends para o apt-get para não instalar os recomendados, somente irá instalar os obrigatórios.
FROM debian
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
wget \
ssh \
vim
COPY . .
ENTRYPOINT bash
Dica 5 - Remover o cache do gerenciador de pacotes
Quando faz um update do sistema cria-se arquivos em /var/lib/apt/list e em /var/cache/apt vamos fazer uma analise disso. Só ai temos muitos megas, além do que existem alguns pacotes .deb que não deviam estar ali gerando vulnerabilidades.
Analise outros gerenciadores caso não esteja utilizando uma distro baseada em debian.
vagrant@master:/var/cache/apt$ sudo du -hs /var/cache/apt
190M /var/cache/apt
vagrant@master:/var/cache/apt$ sudo du -hs /var/lib/apt/lists/
148M /var/lib/apt/lists/
vagrant@master:/var/cache/apt$
vagrant@master:/var/cache/apt$ tree /var/cache/apt
/var/cache/apt
├── archives
│ ├── apt-transport-https_2.0.9_all.deb
│ ├── containerd.io_1.6.6-1_amd64.deb
│ ├── docker-ce-cli_5%3a20.10.17~3-0~ubuntu-focal_amd64.deb
│ ├── docker-ce-rootless-extras_5%3a20.10.17~3-0~ubuntu-focal_amd64.deb
│ ├── docker-ce_5%3a20.10.17~3-0~ubuntu-focal_amd64.deb
│ ├── docker-compose-plugin_2.6.0~ubuntu-focal_amd64.deb
│ ├── docker-scan-plugin_0.17.0~ubuntu-focal_amd64.deb
│ ├── libssl1.1_1.1.1f-1ubuntu2.15_amd64.deb
│ ├── lock
│ ├── openssl_1.1.1f-1ubuntu2.15_amd64.deb
│ ├── partial [error opening dir]
│ ├── slirp4netns_0.4.3-1_amd64.deb
│ └── tree_1.8.0-1_amd64.deb
├── pkgcache.bin
└── srcpkgcache.bin
2 directories, 14 files
Melhorando o dockerfile... Vamos imaginar que o vim não fosse necessário... já vamos retirar.
FROM debian
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
wget \
ssh \
&& rm -rf /var/lib/apt/lists \
&& rm -rf /var/cache/apt
COPY . .
ENTRYPOINT bash
o apt-get clean limpa parcialmente, logo é melhor deletar o diretório na raiz.
buildando a imagem como exemplo 4 onde deletamos as coisas e comparando com o exemplo 2 e 3 já vemos uma diferença.
vagrant@master:~/dockerfiles/exemplo2/image$ docker image list | grep exemplo
exemplo4 latest dddd96c9c835 37 seconds ago 139MB
exemplo2 latest 6ba55366c298 5 hours ago 223MB
exemplo3 latest 6ba55366c298 5 hours ago 223MB
Dica 6 - Utilizem imagens oficiais quando possível
Procure utilizar imagem oficiais se elas já existem. Mas lembrando tem que ser as oficiais pela docker ou do próprio fabricante. Isso garante que a instalação seja feita de acordo e geralmente são as mais limpas.
Dica 7 - Utilizar tag mais específica
Procure garantir uma versão específica da imagem base que vc esta utilizando. Isso evita que vc use a tag latest e caso você venha buildar uma nova imagem pode ser que existam modificações que geram incompatibilidade com algo.
Dica 8 - Procure por flavors mínimos
Dentro de um mesmo fornecedor existem várias versões da mesma imagem. Se procuramos uma imagem com openjdk no dockerhub vemos que temos diferentes tags. Vale a pena uma pesquisa sempre e testar o build com versões mais reduzidas.
Baixe varias imagens diferentes para analisar
docker image pull openjdk:8
docker image pull openjdk:8-jre
docker image pull openjdk:8-jre-slim
docker image pull openjdk:8-jre-alpine
vagrant@master:~/dockerfiles/exemplo2/image$ docker image ls | grep openjdk
openjdk 8-jre 155efed40fd4 3 weeks ago 274MB
openjdk 8 5bf086edab5e 3 weeks ago 526MB
openjdk 8-jre-slim 1211f482e707 3 weeks ago 194MB
openjdk 8-jre-alpine f7a292bbb70c 3 years ago 84.9MB
Veja como a imagem do alpine é bem reduzida.
slim = debian = GNU Libc alpine = Alpine = musl bbc
Dica 9 - Utilizar multi-state build
O multi-state é a capacidade de ter varios FROMS dentro de um dockerfile. Você cria uma imagem por exemplo para buildar o código e retira dessa imagem somente os binários e passa para outro imagem reduzida para rodar.
Vou mostrar um exemplo somente de como funciona através de um projeto .net 6.0 imaginário.
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
# Diretório de entrada no container
WORKDIR /sources
# Copiando todo o suposto código para dentro
COPY . .
# Comandos para gerar os dlls na pasta /src
RUN dotnet restore \
&& dotnet publish meuapp/meuapp.csproj -c release -o /src --no-restore --no-cache
# final stage/image
# Observe que mudei a imagem de sdk para aspnet que funciona só como runtime e é muito menor
FROM mcr.microsoft.com/dotnet/aspnet:6.0
# Diretório de entrada. No caso o copy vai manda para dentro desse diretório
WORKDIR /app
COPY --from=build /src .
CMD ["meuapp.dll"]
É possível utilizar o COPY --from vindo de outra imagem não declarada no mesmo arquivo
COPY --from=imageteste:v1 /src .
Prune
Durante o desenvolvimento da uma imagem criamos varias vezes a mesma tag. Algumas versões acabam perdendo a referencia e ficam com <none> para remover essas imagens basta executar o prune.
docker image prune
docker image prune -a vai remover toda sas imagens que não tem container usando