Docker em 10 minutos

Joe Santos
Engenharia Arquivei
7 min readJan 28, 2022

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.

Diagrama mostrando a arquitetura de um ambiente que usa máquinas virtuais em comparação com a arquitetura de um ambiente Docker.
Diferenças entre máquinas virtuais e contêineres: A arquitetura com máquinas virtuais (esq.) inclui o uso de um sistema operacional virtual onde bibliotecas e outros binários precisam ser instalados para dar suporte à aplicação. Essa arquitetura leva a um ambiente mais pesado que o ambiente que usa contêineres.

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:80
php-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-project
database:
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.

--

--