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 formatodocker 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 chamadocker-compose
em vez dedocker 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 rodardocker-compose
, usamos o comandodocker compose
. Pode coexistir com odocker-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 comodocker-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.
O meu Docker Compose está na versão 2.
docker compose <comando>
docker-compose <comando>
Para ver a versão do docker compose:
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ção | Descrição |
---|---|
--dry-run | Executa o comando no modo de teste (sem execução real). |
--env-file | Especifica um arquivo de ambiente alternativo. |
-f, --file | Arquivos de configuração do Compose. |
--progress | Define o tipo de saída de progresso (auto, tty, plain, quiet). |
--project-directory | Especifica um diretório de trabalho alternativo. |
-p, --project-name | Nome do projeto. |
Tambem existem comandos que podemos executar com o docker compose
, vou deixar os mais comuns:
Comando | Descrição |
---|---|
docker compose build | Construir ou reconstruir serviços. |
docker compose config | Analisar, resolver e renderizar o arquivo compose no formato canônico. |
docker compose cp | Copiar arquivos/pastas entre um contêiner de serviço e o sistema de arquivos local. |
docker compose create | Cria contêineres para um serviço. |
docker compose up | Criar e iniciar contêineres. |
docker compose down | Parar e remover contêineres, redes. |
docker compose events | Receber eventos em tempo real de contêineres. |
docker compose exec | Executar um comando em um contêiner em execução. |
docker compose images | Listar imagens usadas pelos contêineres criados. |
docker compose kill | Parar forçadamente os contêineres de serviço. |
docker compose logs | Visualizar saída dos contêineres. |
docker compose ls | Listar projetos compose em execução. |
docker compose port | Imprimir a porta pública para um bind de porta. |
docker compose ps | Listar contêineres. |
docker compose pull | Puxar imagens de serviço. |
docker compose push | Enviar imagens de serviço. |
docker compose restart | Reiniciar contêineres de serviço. |
docker compose rm | Remover contêineres de serviço parados. |
docker compose run | Executar um comando único em um serviço. |
docker compose start | Iniciar serviços. |
docker compose stop | Parar serviços. |
docker compose top | Exibir os processos em execução. |
docker compose pause | Pausar serviços. |
docker compose unpause | Despausar serviços. |
docker compose version | Mostrar informações da versão do Docker Compose. |
docker compose wait | Bloquear até que o primeiro contêiner de serviço pare. |
docker compose scale | Permite aumentar o número de réplicas de um serviço específico. |
docker compose -f path/file.yaml ACTION | Permite 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:
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) oudocker-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.
Campo | Descrição |
---|---|
version | Esta é 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. |
volumes | Esta seção é usada para definir volumes que podem ser usados por contêineres para persistir dados ou compartilhar dados entre contêineres. |
networks | Esta seção é usada para definir redes personalizadas para os contêineres do seu aplicativo. |
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:
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
:
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.
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, temosportaHost
: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 utilizarenv_file
ao invés de environment. Oenv_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 arquivodocker-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 eC:\<config-name>
em contêineres Windows.
secret
Osecrets
é 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 comandosCMD
eENTRYPOINT
são usados para definir qual comando será executado quando um contêiner for iniciado.Qualquer imagem Docker deve ter uma declaração
ENTRYPOINT
ouCMD
para que um contêiner seja iniciado. Embora as instruçõesENTRYPOINT
eCMD
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çãocommand
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 diretivarestart
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. Quandoexternal
é definido comotrue
, todos os outros atributos além doname
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 config
mas 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 exemploreplicas: 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
ouany
.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.
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:
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:
Atributo | Explicação |
---|---|
driver | Especifica 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_opts | Especifica 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. |
attachable | Se 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_ipv6 | Habilita a rede IPv6. |
external | Se definido como verdadeiro: especifica que o ciclo de vida desta rede é mantido fora do aplicativo. Qualquer outro valor é irrelevante. |
ipam | Especifica uma configuração IPAM personalizada. Para ver como configurar uma rede IPAM e quais opções estão disponíveis, acesse aqui. |
internal | Por padrão, o Compose fornece conectividade externa às redes. internal: true permite criar uma rede isolada externamente. |
labels | Adiciona 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. |
name | Define 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 deservices
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 imagemminha-imagem
. - Ele expõe a porta
80
do contêiner para a porta8080
do host. - Define a variável de ambiente
MYSQL_ROOT_PASSWORD
paraminhaSenha
. - 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 comoalways
, 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
.
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:
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
:
services:
- Esta é uma seção onde você define os diferentes serviços que compõem sua aplicação Docker.
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.
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 chamadomysql_data
é montado em/var/lib/mysql
e um arquivoinit.sql
do host é montado em/docker-entrypoint-initdb.d/init.sql
.networks
: Especifica a rede à qual este serviço pertence.
- Outro serviço chamado
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.
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.
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.
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.
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.
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:
cmd: tree -L 2
.
├── apiweb
│ ├── dockerfile
│ ├── package-lock.json
│ └── proj
├── docker-compose.yaml
├── mysql
│ ├── dockerfile
│ └── init.sql
├── mysql_data
Agora vamos executar:
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:
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:
# 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
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!!
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:
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:
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:
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.
Esse é apenas um exemplo bem simples de como usar esse script!