Skip to main content


Docker Compose


Docker Compose é o orquestrador de containers do Docker. Um orquestrador é uma ferramenta ou plataforma que gerencia e coordena a implantação, operação e escalabilidade de aplicativos distribuídos e contêineres em um ambiente de computação em nuvem ou em um ambiente de infraestrutura distribuída.


O Docker compose usa o arquivo docker-compose.yml para definir e gerenciar aplicativos Docker multi-contêineres. Este arquivo é escrito no formato YAML (YAML Ain't Markup Language), que é uma linguagem de serialização de dados legível por humanos e fácil de entender.


Dentro do arquivo docker-compose.yml, você pode definir vários aspectos do seu aplicativo, incluindo os serviços, redes, volumes e outras configurações necessárias.

É possível criar um serviço com o Compose apontando um arquivo Dockerfile.



Instalando o Docker Compose


A documentação oficial do Docker fornece cenários detalhados para a instalação do Docker Compose.

  • docker-compose-plugin
    Esse pacote é recomendado se você já tem o Docker Engine e o Docker CLI instalados. O Docker Compose V2 é então instalado como um plugin integrado ao CLI do Docker. Após a instalação, os comandos são executados no formato docker compose.

  • Docker Compose standalone
    É o método de instala do Docker Compose como um binário separado (independente do Docker CLI). Este é o método tradicional para usar o Docker Compose.


O Ubuntu possui três pacotes do Docker compose disponíveis:

  • docker-compose
    É o Docker Compose V1 clássico, distribuído como um script independente em Python. Não é um plugin do Docker CLI, ou seja, precisa ser instalado e executado como um binário separado (o plugin se chama docker-compose em vez de docker compose - esse último é executado como um plugin). A versão 1 está obsoleta e foi substituída pelo Docker Compose V2.

  • docker-compose-plugin - (Prefiro sempre usar esse método)
    Já explicado acima, é o plugin oficial do Docker CLI para o Docker Compose V2. Por ser um plugin do Docker CLI, é integrado diretamente ao CLI do Docker. Em vez de rodar docker-compose, usamos o comando docker compose. Pode coexistir com o docker-compose V1, mas é recomendável usar apenas um dos dois para evitar confusão.

    Como já disse, esse pacote é recomendado se você já tem o Docker Engine e o Docker CLI instalados.

  • docker-compose-v2
    Esse é um binário separado do Docker Compose. Funciona como uma versão paralela, instalada separadamente e utilizada como docker-compose (similar à V1, mas é V2). É útil em sistemas onde o plugin do Docker CLI (docker-compose-plugin) não está disponível ou não pode ser utilizado.


Sintaxe do Docker Compose

O meu Docker Compose está na versão 2.


Sintaxe do Docker Compose instalado como plugin
docker compose <comando>

Sintaxe do Docker Compose usando o binário
docker-compose <comando>

Para ver a versão do docker compose:

Terminal
dpkg -l | grep compose
ii docker-compose-plugin 2.25.0-1~ubuntu.22.04~jammy amd64 Docker Compose (V2) plugin for the Docker CLI.


Docker Compose v1 e v2


A primeira versão do binário de CLI do Docker Compose era escrito em Python. O arquivo do Docker Compose V1 começava com o elemento version como sendo a primeira linha no arquivo docker-compose.yml, este campo especificava a versão do formato do arquivo Compose, que determinava como os serviços e configurações eram definidos. Os valores podiam variar entre 2.0 e 3.8, cada um associado a diferentes recursos e compatibilidades.


O Docker Compose V2, anunciado em 2020 e reescrito em Go, trouxe diversas melhorias em relação à versão anterior, incluindo a remoção da obrigatoriedade do campo version no arquivo docker-compose.yml. No Compose V2, você ainda pode especificar o campo version no arquivo YAML se desejar, mas ele não é mais obrigatório, pois o Compose agora infere automaticamente a versão do formato com base na estrutura e no conteúdo do arquivo. Isso simplifica a configuração, especialmente para novos projetos.


A seguir, exemplos básicos comparando a sintaxe entre o Compose V1 e V2. Docker Compose v2:

version: '3.8'

services:
apiweb:
build:
context: apiweb
dockerfile: dockerfile
restart: on-failure
ports:
- "80:80"
networks:
mynetwork:

mysql:
build:
context: mysql
dockerfile: dockerfile
restart: on-failure
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: nodedb
MYSQL_PASSWORD: mypassword
volumes:
- mysql_data:/var/lib/mysql
networks:
mynetwork:

networks:
mynetwork:
driver: bridge

volumes:
proj:
mysql_data:

Docker Compose v1:

version: '3.8'

services:
apiweb:
build:
context: ./apiweb
dockerfile: dockerfile
restart: on-failure
ports:
- "80:80"
networks:
- mynetwork

mysql:
build:
context: ./mysql
dockerfile: dockerfile
restart: on-failure
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: nodedb
MYSQL_PASSWORD: mypassword
volumes:
- mysql_data:/var/lib/mysql
networks:
- mynetwork

networks:
mynetwork:
driver: bridge

volumes:
proj:
mysql_data:


Parâmetros do Docker Compose


O Docker Compose tem poucos opções na CLI, já que a maior parte da configuração é feita diretamente no arquivo .yaml. No entanto, algumas opções disponíveis podem ser bastante úteis e merecem destaque. Abaixo estão alguns exemplos:


OpçãoDescrição
--dry-runExecuta o comando no modo de teste (sem execução real).
--env-fileEspecifica um arquivo de ambiente alternativo.
-f, --fileArquivos de configuração do Compose.
--progressDefine o tipo de saída de progresso (auto, tty, plain, quiet).
--project-directoryEspecifica um diretório de trabalho alternativo.
-p, --project-nameNome do projeto.

Tambem existem comandos que podemos executar com o docker compose, vou deixar os mais comuns:


ComandoDescrição
docker compose buildConstruir ou reconstruir serviços.
docker compose configAnalisar, resolver e renderizar o arquivo compose no formato canônico.
docker compose cpCopiar arquivos/pastas entre um contêiner de serviço e o sistema de arquivos local.
docker compose createCria contêineres para um serviço.
docker compose upCriar e iniciar contêineres.
docker compose downParar e remover contêineres, redes.
docker compose eventsReceber eventos em tempo real de contêineres.
docker compose execExecutar um comando em um contêiner em execução.
docker compose imagesListar imagens usadas pelos contêineres criados.
docker compose killParar forçadamente os contêineres de serviço.
docker compose logsVisualizar saída dos contêineres.
docker compose lsListar projetos compose em execução.
docker compose portImprimir a porta pública para um bind de porta.
docker compose psListar contêineres.
docker compose pullPuxar imagens de serviço.
docker compose pushEnviar imagens de serviço.
docker compose restartReiniciar contêineres de serviço.
docker compose rmRemover contêineres de serviço parados.
docker compose runExecutar um comando único em um serviço.
docker compose startIniciar serviços.
docker compose stopParar serviços.
docker compose topExibir os processos em execução.
docker compose pausePausar serviços.
docker compose unpauseDespausar serviços.
docker compose versionMostrar informações da versão do Docker Compose.
docker compose waitBloquear até que o primeiro contêiner de serviço pare.
docker compose scalePermite aumentar o número de réplicas de um serviço específico.
docker compose -f path/file.yaml ACTIONPermite especificar um arquivo de configuração personalizado para o Docker Compose. Este parâmetro é seguido pelo caminho para o arquivo de configuração. As ações foram descritas nas opções acima.


Fazendo build de imagens no Compose


No arquivo docker-compose.yml, nós podemos especificar que um serviço deve ser construído a partir de um Dockerfile, para isso, temos que usar a opção build, que deve indicar o caminho do Dockerfile e o contexto de build (o diretório base para o processo de construção). Abaixo podemos ver um exemplo:


docker-compose.yaml
services:
app:
build:
context: .
dockerfile: Dockerfile

O Docker Compose só construirá uma nova imagem nas seguintes situações:

  • Alterações no Dockerfile: Se o arquivo Dockerfile especificado (ex.: dockerfile: Dockerfile) for modificado.
  • Alterações no contexto de build: Se qualquer arquivo ou diretório dentro do caminho especificado no context sofrer alteração.
  • Se você forçar a reconstrução: Usando o comando docker-compose build (para (re)criar a imagem) ou docker-compose up --build (para (re)criar e já efetuar o deploy).

O Docker Compose, ao realizar o build de imagens, se beneficia do mesmo mecanismo de camadas do Docker para gerenciar o cache. O Docker Compose rastreia alterações nos arquivos dentro do contexto especificado. Se algum arquivo no contexto mudar, o Compose considera que o cache pode não ser válido e repassa essas alterações ao mecanismo do Docker.


Se o Dockerfile ou os comandos contidos nele forem alterados, o Compose também considera que uma nova imagem precisa ser construída. O Docker Compose calcula hashes ou checksums baseados nos arquivos do contexto e no Dockerfile para determinar se algo mudou. Isso é mais um nível de rastreamento gerenciado pelo Compose antes de passar para o Docker.



Seções do docker-compose.yml


Abaixo podemos ver algumas das principais seções e funcionalidades que são definidas dentro do arquivo docker-compose.yml, essas são as seções mais comuns encontradas em uso com o Docker Compose.

CampoDescrição
versionEsta é a primeira linha do arquivo docker-compose.yml e define a versão do arquivo docker-compose.yml que o Docker Compose está usando (o padrão é 3.8). Você pode ver as opções que cada versão fornece aqui.
servicesÉ onde definimos os diferentes serviços que compõem o aplicativo Docker. Cada serviço é um contêiner Docker que executa uma parte específica do seu aplicativo. O bloco services é um dos blocos mais importantes e frequentemente utilizados no Docker Compose.
volumesEsta seção é usada para definir volumes que podem ser usados por contêineres para persistir dados ou compartilhar dados entre contêineres.
networksEsta seção é usada para definir redes personalizadas para os contêineres do seu aplicativo.

Diretiva include

A diretiva include no Docker Compose permite que você reutilize outros arquivos de composição do Docker ou fatorize partes do modelo do seu aplicativo em arquivos separados. Isso é útil em situações em que seu aplicativo Docker Compose depende de configurações ou serviços definidos em outros arquivos de composição ou se você deseja dividir seu arquivo de composição em partes menores e mais gerenciáveis.


Exemplo:

docker-compose.yml
version: '3.8'

services:
frontend:
# Definição do serviço frontend

networks:
# Definição de redes

include:
- services.yml
- networks.yml


Nome de Projeto


No Compose, o nome padrão do projeto é derivado do nome base do diretório do projeto, ou seja, se o diretório se chama pye1, o nome do projeto será pye1. No entanto, você tem flexibilidade para definir um nome de projeto personalizado. Para ver como definir, veja aqui.


Uma das formas mais simples é definir parâmetro COMPOSE_PROJECT_NAME no docker-compose.yml. Esse nome é importante porque é utilizado como um prefixo para os nomes de contêineres, redes, volumes e outros recursos gerados pelo Docker Compose. Você pode definir o nome do projeto no arquivo .env, que deve ficar no mesmo diretório do arquivo docker-compose.yml.


Você também pode especificar o nome do projeto diretamente ao executar um comando Docker Compose, usando a flag --project-name ou apenas -p:

Terminal
docker compose --project-name meu_projeto up

Também podemos definir o nome do projeto diretamente no arquivo docker-compose.yml usando o campo name no topo do arquivo.

docker-compose.yaml
name: meu_projeto_personalizado

services:
web:
image: nginx
ports:
- "80:80"


Services


Dentro do bloco services, você lista os serviços que deseja definir, cada um com suas próprias configurações. Cada serviço é identificado por um nome único e pode ter várias configurações associadas a ele. Algumas das configurações comuns que você pode definir para cada serviço incluem:

  • image
    A imagem Docker a ser usada para criar o contêiner do serviço.

  • ports
    As portas TCP ou UDP que o contêiner do serviço expõe para permitir a comunicação com outros contêineres ou serviços. Nesse campo, temos portaHost:portaContainer.

  • environment
    Variáveis de ambiente que serão passadas para o contêiner do serviço durante a execução. Muitos serviços podem deixar variáveis vazias para que sejam definidos os valores externos. É possível utilizar env_file ao invés de environment. O env_file pode apontar pra um arquivo .env qualquer dentro do host.

  • volumes
    Diretórios ou pontos de montagem que são montados dentro do contêiner do serviço. Esses volumes são usados para persistir dados ou compartilhar dados entre contêineres. Eles são declarados assim: - /path/on/host:/path/in/container:ro.

  • networks
    Atribuição do serviço a uma ou mais redes definidas no arquivo docker-compose.yml.

  • config
    As configurações (configs) são montadas como arquivos no sistema de arquivos do contêiner de um serviço. O local do ponto de montagem dentro do contêiner é padronizado como /<config-name> em contêineres Linux e C:\<config-name> em contêineres Windows.

  • secret
    O secrets é uma maneira de fornecer dados sensíveis, como senhas, chaves SSH ou certificados TLS, de forma segura aos serviços em contêineres sem expô-los diretamente no arquivo de configuração do Compose ou no Dockerfile. Isso é especialmente útil quando você precisa compartilhar informações confidenciais entre vários serviços ou contêineres.

  • CMD, entrypoint
    No Docker, os comandos CMD e ENTRYPOINT são usados para definir qual comando será executado quando um contêiner for iniciado.

    Qualquer imagem Docker deve ter uma declaração ENTRYPOINT ou CMD para que um contêiner seja iniciado. Embora as instruções ENTRYPOINT e CMD possam parecer semelhantes à primeira vista, existem diferenças fundamentais na forma como elas constroem imagens de contêiner.

    O CMD define o comando padrão a ser executado quando o contêiner é iniciado. É possível substituir esses parâmetros na CLI do Docker durante a execução do contêiner.

    O ENTRYPOINT define o comando principal a ser executado quando o contêiner é iniciado.

  • command
    A opção command dentro de um serviço permite especificar qual comando deve ser executado quando o contêiner associado a esse serviço for iniciado.

  • depends_on
    Definição de dependências, significa que uma aplicação só poderia ser iniciada depois que outra aplicação estiver UP.

  • restart
    É usado para especificar o comportamento de reinicialização do contêiner associado a esse serviço em caso de falha ou reinicialização do Docker. Existem várias opções que você pode usar com a diretiva restart para definir o comportamento de reinicialização do contêiner:

    • no:
      Esta é a opção padrão e significa que o contêiner não será reiniciado automaticamente em caso de falha.

    • always
      Esta opção garante que o contêiner seja sempre reiniciado automaticamente, independentemente do motivo da falha.

    • on-failure
      Esta opção especifica que o contêiner será reiniciado automaticamente apenas se ele sair com um código de erro não zero.

    • unless-stopped
      Esta opção garante que o contêiner seja reiniciado automaticamente sempre que ele sair, a menos que seja explicitamente parado pelo usuário.



Opção Config


Para que os serviços consigam acessar as configurações, é necessário colocar explicitamente um atributo configs dentro do elemento de nível superior de services. Podemos trabalhar com configurações locais, onde a configação fica no compose.yaml ou configurações externas, onde as configurações ficam num arquivo externo.


Quando é definido uma configuração externa usando o atributo external: true, isso indica ao sistema que a configuração já existe em algum lugar fora do docker-compose.yml. O Docker Compose não é responsável por criar ou gerenciar essas configurações externas, apenas as utiliza.


Se a configuração não for declarada como external: true, isso significa que a configuração está dentro do arquivo docker-compose.yml.


A configuração pertence ao usuário que executa o comando do contêiner mas pode ser substituída pela configuração do serviço. Possui permissões legíveis por todos (modo 0444), a menos que o serviço esteja configurado para substituir isso.


A declaração de configs de nível superior define ou faz referência a dados de configuração concedidos aos serviços em sua aplicação Compose. A origem da configuração é file ou external.

  • file: A configuração é criada com o conteúdo do arquivo no caminho especificado.

  • environment: O conteúdo da configuração é criado com o valor de uma variável de ambiente.

  • content: O conteúdo é criado com o valor inline.

  • external: Se definido como true, especifica que esta configuração já foi criada. O Compose não tenta criá-la e, se não existir, ocorrerá um erro. Quando external é definido como true, todos os outros atributos além do name serão irrelevantes.

  • name: O nome do objeto de configuração no mecanismo de contêineres para procurar. Este campo pode ser usado para referenciar configurações que contenham caracteres especiais. O nome é utilizado como está e não será escopo com o nome do projeto.


O <project_name>_http_config é criado quando o aplicativo é implementado, registrando o conteúdo do httpd.conf como os dados de configuração. Isso de certa forma faz referencia a uma configuração externa.

configs:
http_config:
file: ./httpd.conf

Alternativamente, http_config pode ser declarado como externo. O Compose procura http_config para expor os dados de configuração aos serviços relevantes.

configs:
http_config:
external: true

O <project_name>_app_config é criado quando o aplicativo é implementado, registrando o conteúdo embutido como dados de configuração. Isso significa que o Compose infere variáveis ao criar a configuração, o que permite ajustar o conteúdo de acordo com a configuração do serviço:

configs:
app_config:
content: |
debug=${DEBUG}
spring.application.admin.enabled=${DEBUG}
spring.application.name=${COMPOSE_PROJECT_NAME}

Exemplo completo:

services:
frontend:
image: example/webapp
ports:
- "443:8043"
configs:
- my-config

configs:
my-config:
file: ./path/to/config/file.conf


Opção Secret


O secrets é uma variante do configmas o foco são os dados confidenciais, com restrições específicas para esse uso. Os serviços só podem acessar segredos quando explicitamente concedidos por um atributo secrets dentro do elemento de nível superior services. Em outras palabras, secret é qualquer dado, como uma senha, certificado ou chave de API, que não deve ser transmitido por uma rede ou armazenado sem criptografia em um Dockerfile ou no código-fonte do seu aplicativo.


A declaração secret de nível superior define ou faz referência a dados sensíveis concedidos aos serviços em sua aplicação Compose. A origem do segredo é um arquivo ou ambiente.

  • file: O secrets é criado com o conteúdo do arquivo no caminho especificado.
  • environment: O secrets é criado com o valor de uma variável de ambiente.

O secret chamado server-certificate abaixo é criado como <project_name>_server-certificate quando o aplicativo é implementado, registrando o conteúdo do server.cert como um segredo de plataforma.

secrets:
server-certificate:
file: ./server.cert

O secret chamado token abaixo é criado como <project_name>_token quando o aplicativo é implementado, registrando o conteúdo da variável de ambiente OAUTH_TOKEN como um segredo de plataforma.

secrets:
token:
environment: "OAUTH_TOKEN"

Exemplo completo:

services:
myapp:
image: myapp:latest
secrets:
- my_secret

secrets:
my_secret:
file: ./my_secret.txt

Para mais detalhes de Screts, vejam os links abaixo:

https://docs.docker.com/compose/use-secrets/
https://docs.docker.com/compose/compose-file/05-services/#secrets



Opção Deploy


A opção deploy no Docker Compose é usada para configurar políticas de implantação e orquestração de serviços quando você está utilizando ferramentas de orquestração, como o Docker Swarm. Ela não tem efeito em implantações locais normais feitas com docker compose up sem Swarm.


Essa seção permite configurar comportamentos avançados, como réplicas, limites de recursos, estratégias de atualização e regras de colocação de serviços no cluster. Exemplo Básico:

services:
web:
image: nginx
deploy:
replicas: 3
resources:
limits:
cpus: "0.5"
memory: "512M"
reservations:
cpus: "0.25"
memory: "256M"
restart_policy:
condition: on-failure
update_config:
parallelism: 2
delay: 10s
placement:
constraints:
- node.role == manager

  • replicas
    Define o número de instâncias do serviço a serem executadas no cluster. O exemplo replicas: 3 cria três réplicas do serviço.

  • resources
    Define limites e reservas de recursos para o serviço.

    • limits: Especifica o máximo de CPU e memória que o serviço pode usar.
    • reservations: Garante a alocação mínima de CPU e memória para o serviço.
  • restart_policy
    Configura a política de reinício para os contêineres. Condições incluem: none, on-failure ou any.

  • update_config
    Define como os serviços devem ser atualizados.

    • parallelism: Número de réplicas que podem ser atualizadas simultaneamente.
    • delay: Tempo de espera entre as atualizações de réplicas.
  • placement
    Especifica restrições de onde o serviço pode ser executado. Um exemplo é node.role == manager, isso garante que o serviço seja executado apenas nos nós do tipo manager.



Network


Existem dois elementos para network, um de nível superior que permite configurar redes nomeadas que podem ser reutilizadas em vários serviços. Já o segundo elemento, deve ser usado em conjunto como atributo e deve ser colocado no elemento de nível superior services.


Por padrão, o Compose configura uma única rede para seu aplicativo. Cada contêiner de um serviço ingressa na rede padrão e pode ser acessado por outros contêineres nessa rede e descoberto pelo nome do serviço.


Em vez de especificar suas próprias redes, você também pode alterar as configurações da rede padrão de todo o aplicativo definindo uma entrada em redes chamada padrão. O exemplo abaixo mostra como você pode alterar as configurações da rede padrão, no caso, mudando o driver a ser usado.

docker-compose.yaml
version: '3.8'

services:
web:
build: .
ports:
- "8000:8000"
db:
image: postgres

networks:
default:
# Use a custom driver
driver: custom-driver-1

Veja o exemplo abaixo:

docker-compose.yaml
version: '3.8'

services:
frontend:
image: example/webapp
networks:
- front-tier
- back-tier

networks:
front-tier:
back-tier:

Inicialmente o frontend está conectao em duas redes: front-tier e back-tier. Caso essas redes existam, as configurações de cada rede será aplicada. Mas se você não criou explicitamente as redes back-tier e front-tier antes de execupar o compose, cada uma das redes usarão a rede padrão do Docker Compose para se comunicar. Essa rede padrão é criada automaticamente pelo Docker Compose para cada projeto e é chamada de default.


Dentro do elementos network no nível superior, podemos configurar alguns atributos, a tabela abaixo mostra quais são:

AtributoExplicação
driverEspecifica qual driver deve ser usado para essa rede. O Docker Compose retorna um erro se o driver não estiver disponível na plataforma.
driver_optsEspecifica uma lista de opções como pares chave-valor para passar para o driver. Essas opções dependem do driver. Consulte a documentação do driver para obter mais informações.
attachableSe definido como verdadeiro, contêineres independentes devem poder se conectar a essa rede, além dos serviços. Se um contêiner independente se conectar à rede, ele poderá se comunicar com serviços e outros contêineres independentes que também estão conectados à rede.
enable_ipv6Habilita a rede IPv6.
externalSe definido como verdadeiro: especifica que o ciclo de vida desta rede é mantido fora do aplicativo. Qualquer outro valor é irrelevante.
ipamEspecifica uma configuração IPAM personalizada. Para ver como configurar uma rede IPAM e quais opções estão disponíveis, acesse aqui.
internalPor padrão, o Compose fornece conectividade externa às redes. internal: true permite criar uma rede isolada externamente.
labelsAdiciona metadados aos contêineres usando rótulos. É recomendável usar a notação de DNS reverso para evitar conflitos com rótulos usados por outros softwares.
nameDefine um nome personalizado para a rede.

IPAM (IP Address Management) refere-se ao processo de gerenciamento de endereços IP dentro de uma rede. Isso inclui a atribuição de endereços IP para contêineres, a configuração de sub-redes e faixas de endereços IP disponíveis, bem como a configuração de gateways e outros parâmetros de rede.


Em network dentro de services podemos sspecificar um endereço IP estático para um contêiner de serviço usar quando ele ingressar na rede. Para essa configuração , veja aqui.



Exemplo de docker compose


Aqui está um exemplo simples de um arquivo docker-compose.yml que usa todas as opções descritas anteriormente:

version: '3.8'

services:
meu-servico:
image: minha-imagem
ports:
- "8080:80"
environment:
- MYSQL_ROOT_PASSWORD=minhaSenha
volumes:
- meu-volume:/app/data
networks:
- minha-rede
restart: always

networks:
minha-rede:

volumes:
meu-volume:

  • O serviço meu-servico é definido para usar a imagem minha-imagem.
  • Ele expõe a porta 80 do contêiner para a porta 8080 do host.
  • Define a variável de ambiente MYSQL_ROOT_PASSWORD para minhaSenha.
  • Monta um volume chamado meu-volume no caminho /app/data dentro do contêiner.
  • É atribuído à rede personalizada minha-rede.
  • A opção restart é configurada como always, o que garante que o contêiner seja sempre reiniciado automaticamente em caso de falha.

    Isso é apenas um exemplo básico e pode ser expandido conforme necessário para atender às necessidades específicas do seu aplicativo Docker.



Docker Compose na prática


Vamos criar uma aplicação usando o Docker Compose. A aplicação deve ter um banco de dados, uma API (Back-end) e uma aplicação web (Front-end). Ambas devem conversar entre si e utilizar a mesma rede criada pelo arquivo docker-compose.yml.


A configuração pode estar separada em Dockerfiles ou descritas totalmente no docker-compose.yml.

docker-compose.yaml
version: '3.8'

services:
apiweb:
image: node:21-alpine
restart: on-failure
ports:
- "80:80"
volumes:
- ./proj:/app/data
networks:
mynetwork:
command: node /app/data/index.js

mysql:
image: mysql:8.1.0
restart: on-failure
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: nodedb # Cria o banco de dados
MYSQL_PASSWORD: mypassword
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
mynetwork:

networks:
mynetwork:
driver: bridge

volumes:
proj:
mysql_data:

documentação oficial

Usando o container do mysql como exemplo, o docker hub do Mysql possui todos os dados para subir o container do mysql, incluindo as variáveis de ambientes que podemos usar. É fundamental que seja lido a página da documentação do container que está usando.


No começo da página ainda é exibido um link para o dockerfile, dessa forma, podemos ver como a imagem foi criada.


Abaixo segue uma descrição de cada opção usada no arquivo docker-compose.yml:

  1. services:

    • Esta é uma seção onde você define os diferentes serviços que compõem sua aplicação Docker.
  2. apiweb:

    • Este é o nome do serviço.
    • image: Define a imagem a ser usada para criar este contêiner. Neste caso, é node:21-alpine.
    • restart: Define a política de reinicialização do contêiner em caso de falha.
    • ports: Mapeia as portas do contêiner para o host. Neste caso, mapeia a porta 80 do contêiner para a porta 80 do host.
    • volumes: Define os volumes a serem montados no contêiner. Aqui, o diretório ./proj do host é montado em /app/data do contêiner.
    • networks: Especifica a rede à qual este serviço pertence.
    • command: Define o comando a ser executado quando o contêiner é iniciado. Neste caso, é node /app/data/index.js. Sem isso o container vai morrer.
  3. mysql:

    • Outro serviço chamado mysql.
    • image: Define a imagem a ser usada para criar este contêiner. Aqui, é mysql:8.1.0.
    • restart: Define a política de reinicialização do contêiner em caso de falha.
    • environment: Define as variáveis de ambiente necessárias para o contêiner MySQL.
    • volumes: Define os volumes a serem montados no contêiner. Aqui, um volume chamado mysql_data é montado em /var/lib/mysql e um arquivo init.sql do host é montado em /docker-entrypoint-initdb.d/init.sql.
    • networks: Especifica a rede à qual este serviço pertence.
  4. networks:

    • Esta seção define as redes personalizadas para os contêineres.
    • mynetwork: Nome da rede definida.
      • driver: Define o driver de rede a ser usado para esta rede. Aqui, é bridge, que é um driver padrão do Docker.
  5. volumes:

    • Esta seção define os volumes que podem ser usados pelos contêineres.
    • proj: Define um volume nomeado.
    • mysql_data: Define outro volume nomeado.

No diretório proj, tenho os códigos em NodeJS que subirá a aplicação, não cabe nesse momento fornecer os códigos. Um outro detalhe é que é usado um script SQL para criar a tabela e fazer um input na tabela. Poderia deixar tudo dentro do código Node mas como um exemplo simples, achei melhor deixar fora.


Abaixo segue o script sql usado.

init.sql
CREATE TABLE IF NOT EXISTS nodedb (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);

INSERT INTO nodedb (name) VALUES ('FULANO');


Melhorando o Docker Compose


Agora vamos melhorar isso, já que não é criado uma imagem Docker, e sim apenas o container usando uma imagem base. Vamos melhorar o docker compose, usar o dockerfile para criar a imagem e o docker compose vai usar essas imagens para criar o container. Depois de criadas, vamos rodar a ferramenta Trivy para analisar e detalhar o que cada imagem gerou de vulnerabilidade.


Crie o dockerfile para o serviço chamado apiweb.

apiweb/dockerfile
FROM node:21-alpine

COPY ./proj /app/data

WORKDIR /app/data

ENTRYPOINT ["node", "/app/data/index.js"]

Crie o dockerfile para o serviço chamado mysql.

mysql/dockerfile
FROM mysql:8.1.0

COPY ./init.sql /docker-entrypoint-initdb.d/init.sql

Agora modifique o docker-compose para fazer o build do dockerfile e criar o container.

docker-compose.yaml
version: '3.8'

services:
apiweb:
build:
context: apiweb
dockerfile: dockerfile
restart: on-failure
ports:
- "80:80"
networks:
mynetwork:

mysql:
build:
context: mysql
dockerfile: dockerfile
restart: on-failure
environment:
MYSQL_ROOT_PASSWORD: examplepassword
MYSQL_DATABASE: nodedb
MYSQL_PASSWORD: mypassword
volumes:
- mysql_data:/var/lib/mysql
networks:
mynetwork:

networks:
mynetwork:
driver: bridge

A estrutura com os diretórios ficaram assim:

Terminal
cmd: tree -L 2
.
├── apiweb
│   ├── dockerfile
│   ├── package-lock.json
│   └── proj
├── docker-compose.yaml
├── mysql
│   ├── dockerfile
│   └── init.sql
├── mysql_data

Agora vamos executar:

Terminal
docker compose up -d
[+] Running 2/3
⠹ Network teste_mynetwork Created 0.3s
✔ Container teste-apiweb-1 Started 0.2s
✔ Container teste-mysql-1 Started

Acesse o browser via curl para verificar se está funcionando:

Terminal
curl -sk http://192.168.121.122/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sequelize</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Usuários cadastrados</h1>
<div class="container">
<div class="item">
<p>FULANO</p>
</div>

</div>
</body>
</html>

Agora vamos verificar se existem vulnerabilidades:

Terminal
# Verificando vulnerabilidades na imagem 'apiweb':
trivy image --severity CRITICAL,HIGH teste-apiweb
2024-03-31T17:16:53.703Z INFO Vulnerability scanning is enabled
2024-03-31T17:16:53.704Z INFO Secret scanning is enabled
2024-03-31T17:16:53.704Z INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2024-03-31T17:16:53.704Z INFO Please see also https://aquasecurity.github.io/trivy/v0.50/docs/scanner/secret/#recommendation for faster secret detection
2024-03-31T17:16:53.712Z INFO Detected OS: alpine
2024-03-31T17:16:53.712Z INFO Detecting Alpine vulnerabilities...
2024-03-31T17:16:53.713Z INFO Number of language-specific files: 1
2024-03-31T17:16:53.713Z INFO Detecting node-pkg vulnerabilities...

teste-apiweb (alpine 3.19.1)

Total: 0 (HIGH: 0, CRITICAL: 0)


# Verificando vulnerabilidades na imagem 'mysql':
trivy image --severity CRITICAL,HIGH teste-mysql
2024-03-31T17:17:33.473Z INFO Vulnerability scanning is enabled
2024-03-31T17:17:33.473Z INFO Secret scanning is enabled
2024-03-31T17:17:33.473Z INFO If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2024-03-31T17:17:33.473Z INFO Please see also https://aquasecurity.github.io/trivy/v0.50/docs/scanner/secret/#recommendation for faster secret detection
2024-03-31T17:17:33.521Z INFO Detected OS: oracle
2024-03-31T17:17:33.521Z INFO Detecting Oracle Linux vulnerabilities...
2024-03-31T17:17:33.523Z INFO Number of language-specific files: 2
2024-03-31T17:17:33.523Z INFO Detecting gobinary vulnerabilities...
2024-03-31T17:17:33.523Z INFO Detecting python-pkg vulnerabilities...

teste-mysql (oracle 8.8)

Total: 3 (HIGH: 3, CRITICAL: 0)

# Obtendo apenas os CVEs do mysql:
trivy image --severity CRITICAL,HIGH teste-mysql | grep -Eio 'cve-[0-9]{4}-[0-9]{5}' | sort | uniq
CVE-2019-19921
cve-2021-40528
CVE-2021-40528
cve-2023-27561
CVE-2023-27561
cve-2023-37920
CVE-2023-37920
cve-2023-40217
CVE-2023-40217
cve-2023-50782
CVE-2023-50782
cve-2024-21626
CVE-2024-21626
cve-2024-26130
CVE-2024-26130


Exemplo de Docker compose para MkDocs


docker-compose.yaml
services:
mkdocs:
build:
context: . # Diretório onde o Dockerfile está localizado
dockerfile: mkdocs-dockerfile # Nome do Dockerfile que você está usando
restart: on-failure
container_name: mkdocs
volumes:
# - ./mkdocs:/app/data # Usado para criar o projeto!!
- ./mkdocs/courses:/app/data # usado quando o projeto já existe!!
working_dir: /app/data
user: "1000:1000"
expose:
- "8000"
ports:
- 8000:8000
command: >
sh -c 'pip install Pygments --break-system-packages && mkdocs serve -a 0.0.0.0:8000' # usado quando o projeto já existe!!
#sh -c 'mkdocs new courses;' # Usado para criar o projeto!!

mkdocs-dockerfile
FROM alpine:latest

# Instalar shadow (necessário para adduser)
RUN apk add --no-cache shadow python3 py3-pip

# Instalar mkdocs-material
RUN pip install mkdocs-material --break-system-packages

# Criar um usuário não-root com UID 1000
RUN adduser -D -u 1000 -g 1000 -s /bin/sh -h /app/data mkdocsuser

# Definir permissões para o novo usuário no diretório
RUN chown -R mkdocsuser:mkdocsuser /app/data

RUN chmod 770 -R /app/data

# Definir o diretório de trabalho
WORKDIR /app/data

# Mudar o usuário padrão para o não-root criado
USER mkdocsuser

# Expor a porta 8000 para acesso ao MkDocs
EXPOSE 8000

# Rodar pela primeira vez para criar o projeto:
#CMD ["mkdocs", "new", "courses"]


wait-for-it.sh


O script wait-for-it.sh é uma ferramenta útil para garantir que um serviço dependente (como um banco de dados) esteja pronto antes de iniciar outro serviço, especialmente em ambientes Docker com várias dependências. O wait-for-it.sh é um script bash que verifica se um serviço está acessível em uma porta TCP específica antes de prosseguir. Ele é recomendado na documentação oficial do Docker para lidar com dependências de inicialização.


Se você não usar o wait-for-it.sh ou uma abordagem semelhante para garantir que um serviço dependente, como um banco de dados, esteja pronto antes de o serviço principal ser iniciado, poderá enfrentar erros de conexão. Isso ocorre porque, embora o contêiner do banco de dados esteja em execução (UP), a aplicação dentro dele pode ainda não estar totalmente inicializada e pronta para receber conexões.


O que o script wait-for-it.sh faz é simples, mas crucial: ele aguarda até que o serviço dentro do contêiner (como o banco de dados) esteja funcional, garantindo que o serviço principal só seja iniciado quando a aplicação dependente estiver pronta para se comunicar. Isso evita erros e falhas relacionadas à tentativa de conexão prematura. Para baixá-lo:


Terminal
wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
chmod +x wait-for-it.sh

Usando ele com Alpine para testar o banco de dados:

Dockerfile-alpine
FROM alpine:latest
COPY ./wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh

Agora no compose faça:

docker-compose.yaml
version: '3.8'

services:

alpine:
build:
context: .
dockerfile: Dockerfile-alpine
ports:
- 8080:2368
environment:
database__client: mysql
database__connection__host: db
database__connection__user: root
database__connection__password: senha
database__connection__database: dbtest
entrypoint: ["wait-for-it.sh", "db:3306", "--timeout=300", "--", "docker-entrypoint.sh"]
command: ["node", "current/index.js"]
depends_on:
- db

db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: senha
volumes:
- "db:/var/lib/mysql"
networks:
- private

O pulo do gato está em usar o entrypoint como entrypoint: ["wait-for-it.sh", "db:3306", "--timeout=300", "--", "docker-entrypoint.sh"], o entrypoint deve ser ajustado para incluir o script wait-for-it.sh. Esse script verifica se o banco de dados está disponível na porta 3306 antes de continuar. O parâmetro --timeout=300 especifica que o script irá aguardar até 300 segundos antes de desistir.


Atenção

Esse é apenas um exemplo bem simples de como usar esse script!