Docker em 10 minutos
Porque às vezes o tempo é realmente curto.
Introdução
No fim de 2018 eu estava entrando na Arquivei e Docker já era um velho conhecido aqui, mas não para mim. Eu precisava aprender essa nova tecnologia e rápido porque ela está em todo lugar. Lembrando dessa época, resolvi criar um guia para o meu eu do passado e para quem no presente se encontre numa situação parecida.
O que é Docker?
Docker é uma ferramenta de manipulação de contêineres, que te ajuda a rodar sua aplicação em um ambiente próximo ao de produção: mesma distro, mesmas libs nas mesmas versões. Isso elimina os problemas do tipo “na minha máquina funciona”. Em outros tempos, usávamos máquinas virtuais para isso, mas contêineres exigem bem menos recursos porque tiram vantagem da capacidade do Linux de rodar processos em namespaces independentes.
O que vamos cobrir?
Vamos cobrir alguns conceitos básicos: contêineres e imagens e vamos ver como usar Dockerfile, a cli e o docker-compose.
Contêineres
Contêineres são uma instância viva que roda a partir de uma imagem. Contêineres podem se comunicar entre si através de uma rede, podem ter suas próprias variáveis de ambiente, se comunicar com mundo externo através de portas e compartilhar arquivos usando volumes.
Imagens
Imagens são arquivos binários contendo tudo que você vai precisar para rodar um contêiner: configurações de rede, volumes, pacotes instalados, etc. Vale notar que imagens são constituídas de camadas. Isso é importante por dois motivos: o primeiro é que qualquer alteração feita em um contêiner afeta a “camada mais acima” da imagem, que é efêmera, ou seja, nenhuma alteração feita em um contêiner afeta a imagem base desse contêiner. O segundo motivo é cache. Uma camada só é reconstruída se o comando que a gera mudar ou se a camada de baixo for reconstruída. Por isso, se algo dentro do seu contêiner não está como deveria (um arquivo que não foi atualizado, por exemplo), verifique se a camada foi de fato reconstruída ou uma versão desatualizada que estava em cache foi utilizada.
O arquivo Dockerfile
O Dockerfile é um arquivo especial. Ele contém os passos para a geração de uma imagem. Cada comando dentro desse arquivo gera uma nova camada, lembre-se disso porque daí que se derivam diversas boas práticas. Nesse arquivo normalmente você vai encontrar essas diretivas:
ARG
Define um argumento a ser aceito na construção da imagem. Normalmente passamos versões como argumentos para facilitar o reuso.
ARG PHP_VERSIONFROM php:${PHP_VERSION}-fpm-alpine
No exemplo acima, a diretiva ARG é utilizada para permitir que o usuário escolha a versão do PHP em tempo de build. Podemos passar também um valor default:
ARG PHP_VERSION=8.1FROM php:${PHP_VERSION}-fpm-alpine
Para passar um valor, na hora de construir a imagem utilizamos o comando:
$ docker build --build-arg PHP_VERSION=7.4 .
Caso não exista um valor default, é necessário passar um valor na hora de construir a imagem.
Argumentos usados na diretiva FROM precisam ser declarados antes dela, outros argumentos devem ser declarados depois:
ARG PHP_VERSIONFROM php:${PHP_VERSION}-fpm-alpineARG LIBRDKAFKA_VERSION...RUN git clone --branch ${LIBRDKAFKA_VERSION} https://github.com/edenhill/librdkafka.git
...
WORKDIR
Determina qual diretório será considerado base. Quando um comando usar o diretório atual (denotado por .), é ao workdir que ele se refere.
WORKDIR /application
RUN mkdir docker # cria o diretório /application/docker
FROM
Usado para definir uma imagem base. Normalmente já existem imagens oficiais com algumas dependências básicas (como a distribuição que queremos, por exemplo) e é mais simples construirmos nossa imagem em cima dessa base.
FROM php:8.1-fpm-alpine
Essa imagem está disponível no Docker Hub oficial do PHP, onde você pode escolher uma tag e conferir o que tem na imagem. Você pode criar suas imagens, subir no Docker Hub e utilizá-las:
FROM arquivei/php:7.3-fpm-debian
A diretiva acima ilustra como usar uma das imagens PHP do Arquivei. Você também pode utilizar repositórios privados como o GCR, mas para isso precisa configurar seu Docker adequadamente.
COPY
Copia arquivos para a imagem. Vale lembrar que por não se tratar de um volume, alterar esse arquivo localmente após a construção da imagem, não vai alterar o arquivo no contêiner.
COPY . /app
RUN
Executa um comando. Como cada diretiva no Dockerfile gera uma camada na imagem, uma boa prática é concatenar os comandos (usando &&) para limitar a quantidade de camadas na imagem final. Outra boa prática é deixar os comandos mais prováveis de mudar por último para aproveitar o cache ao máximo na geração da imagem.
RUN apk update && \apk add git zlib-dev libressl-dev libsasl zstd-dev zstd-static build-base && \git config --global advice.detachedHead false && \apk add bash
ENTRYPOINT
O comando que será executado ao rodar um contêiner a partir da imagem.
ENTRYPOINT php /app/app.php
CMD
Utilizado para definir o comando default a ser executado quando o usuário rodar a imagem.
CMD ["php", "/app/app.php"]
As diferenças e interações entre ENTRYPOINT e CMD podem ser melhor entendidas aqui, mas elas fogem do escopo deste artigo.
Docker CLI
Com a CLI (command line interface que significa interface em linha de comando) do Docker você pode manipular imagens e contêineres. Os comandos mais comuns são os seguintes:
docker build
Utilizado para construir imagens. Use a opção -t para criar a imagem com uma tag.
$ docker build -t imagem:tag .
O exemplo acima supõe que o Dockerfile está no mesmo diretório onde o comando está sendo executado. Um Dockerfile diferente pode ser utilizado:
$ docker build -f ~/other/Dockerfile.dev -t imagem:dev .
docker run
Comando para rodar um container a partir de uma imagem. Existem diversas opções para criar volumes, configurar rede e definir variáveis de ambiente, mas veremos um modo mais simples em breve.
$ docker run hello-world
docker ps
Exibe os contêineres que estão rodando com informações adicionais. Use a opção -a para exibir todos os contêineres.
docker stop
Para um contêiner.
docker pull
Baixa uma imagem.
docker push
Faz o upload de uma imagem.
docker image
Lista comandos para lidar com imagens (como docker image ls
para listar imagens e docker image rm
para remoção)
docker container
Análogo ao docker image
, mas para lidar com contêineres.
docker exec
Executa um comando em um contêiner.
$ docker exec -it debian-container bash
O exemplo acima executa o comando bash no contêiner, permitindo modificar o ambiente, por exemplo.
docker logs
Exibe os logs de um contêiner.
Simplificando a manipulação de contêineres com docker-compose
Muitas vezes temos aplicações que são compostas de diversos contêineres: front, back, banco, cache… além disso, um comando docker para subir um contêiner com diversas configurações não é prático. Para nos ajudar a simplificar esse processo, utilizamos o docker-compose.
Para utilizarmos o docker-compose precisamos entender duas coisas: o arquivo docker-compose.yml e a cli.
docker-compose.yml
Diferente do Dockerfile que determina os passos para a construção de um contêiner, o docker-compose.yml contém as configurações para aplicações multi-contêiner como no exemplo abaixo:
version: "3.8"services:nginx:
image: nginx:alpine
container_name: my-project-nginx
working_dir: /application
volumes:
- .:/application
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 8080:80php-fpm:
build: docker/php-fpm
image: my_project_php-fpm
container_name: my-project-php-fpm
working_dir: /application
volumes:
- .:/application
environment:
- APP_TIMEZONE=America/Sao_Paulo
- GOOGLE_APPLICATION_CREDENTIALS=/storage/gcp-credentials.json
- GOOGLE_CLOUD_PROJECT=my-projectdatabase:
restart: always
container_name: my-project-database
image: postgres:12-alpine
ports:
- 5433:5432
volumes:
- ./data/app:/var/lib/postgresql/data
environment:
PGDATA: /var/lib/postgresql/data
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
TZ: America/Sao_Paulo
Nesse exemplo vemos claramente a primeira do docker-compose: volumes, portas e variáveis de ambiente especificados em um arquivo e não mais como parte de um (possivelmente longo) comando. Apenas algumas coisas específicas que precisamos entender:
version
Define a versão da especificação utilizada no arquivo. No momento em que esse post foi escrito, a versão mais recente é a 3.8 e sua especificação pode ser vista aqui.
build
Determina o caminho para um Dockerfile que não esteja no mesmo diretório do arquivo docker-compose.yml ou que não utilize o nome “Dockerfile” permitindo o uso de diferentes Dockerfiles para construir diferentes contêineres.
image
Nome da imagem a ser utilizada para rodar um contêiner. Caso a imagem não seja uma imagem hospedada no Docker Hub (ou no seu repositório privado de imagens), você precisa especificar o build e o valor de image será usado para dar nome à imagem criada.
restart
Define a política de reinício de um contêiner. Valores comuns são always
para contêineres serem reiniciados sempre (o que inclui após o início do processo docker na sua máquina após você ligar seu computador, então cuidado) e on-failure
para reinício apenas quando o contêiner terminar com erro.
Vale notar que todos os contêineres em um mesmo docker-compose.yml compartilham a mesma rede e podem acessar uns aos outros via http com a URL http://service_name
. Ou seja, para acessar o contêiner my-project-php-fpm no exemplo acima a partir do contêiner my-project-nginx, podemos executar um curl para http://php-fpm
.
CLI
Para usufruirmos do potencial do docker-compose temos alguns comandos que podem nos ajudar:
docker-compose up
Sobe todos os contêineres especificados. Caso eles não existam, eles são construídos antes. Com a opção -d o comando roda em “detached mode” o que significa que o terminal é devolvido para você após os contêineres subirem ou falharem. Sem essa opção os logs serão exibidos e o comando para encerrar o processo (ctrl+c no Linux) encerrará os contêineres.
docker-compose stop
Para todos os contêineres.
docker-compose down
Para e remove todos os contêineres e redes. Com a opção -v também remove os volumes.
docker-compose ps
Lista os contêineres e seus status
docker-compose exec
Executa um comando em um contêiner. Nesse caso usamos o nome do serviço:
$ docker-compose exec nginx bash
docker-compose logs
Exibe os logs de todos os contêineres. Passe o nome de um serviço para ver apenas os logs daquele serviço (com -f para seguir os logs, assim como no comando tail):
$ docker-compose logs -f php-fpm
Conclusão
Espero que esse post te ajude a começar sua jornada no mundo dos contêineres. Lembre-se de ler a documentação assim que possível e que esse guia não contém tudo que é possível fazer com Docker, apenas o que, na minha experiência, é o mais comum para que nenhum novato fique boiando o tanto quanto eu boiei. Até a próxima.