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!
Depends on
No Docker Compose, podemos usar o parâmetro depends_on
com condition: service_healthy
para garantir que o serviço dependente esteja em um estado saudável antes que outro serviço seja iniciado. A condição service_healthy
depende de um check de saúde (healthcheck) que você define no serviço.
O compose abaixo foi retirado do projeto do Mailman via Docker.
services:
mailman-core:
image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
container_name: mailman-core
hostname: mailman-core
restart: unless-stopped
volumes:
- /opt/mailman/core:/opt/mailman/
stop_grace_period: 30s
links:
- database:database
depends_on:
database:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://mailman:mailmanpass@database/mailmandb
- DATABASE_TYPE=postgres
- DATABASE_CLASS=mailman.database.postgresql.PostgreSQLDatabase
- HYPERKITTY_API_KEY=someapikey
ports:
- "127.0.0.1:8001:8001" # API
- "127.0.0.1:8024:8024" # LMTP - incoming emails
networks:
mailman:
database:
environment:
- POSTGRES_DB=mailmandb
- POSTGRES_USER=mailman
- POSTGRES_PASSWORD=mailmanpass
image: postgres:12-alpine
volumes:
- /opt/mailman/database:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready --dbname mailmandb --username mailman"]
interval: 10s
timeout: 5s
retries: 5
O wait-for-it.sh
é um script externo que você pode usar em seu container para aguardar até que um serviço (como o banco de dados) esteja disponível e pronto para aceitar conexões. Ele basicamente faz uma verificação contínua de um endereço e porta até que o serviço esteja acessível, por exemplo, verificando se o banco de dados responde na porta configurada.
Ambos wait-for-it.sh
e depends_on
têm o mesmo propósito de garantir que um serviço esteja pronto antes que outro serviço dependa dele.
A diferença é que o depends_on
usado com condition: service_healthy
é uma solução nativa do Compose, usando a verificação de saúde configurada diretamente no serviço. É mais simples e gerenciado pelo próprio Docker Compose.
Já o wait-for-it.sh
é um script externo que precisa ser executado manualmente e é mais flexível, permitindo que você aguarde até que um serviço esteja acessível em uma porta específica. Não depende de healthcheck
integrado ao Docker, e você tem que configurar a lógica de espera manualmente.
Quando você usa o parâmetro depends_on
com a opção condition
, o valor pode ser um dos seguintes:
service_healthy
O serviço dependente só será iniciado quando o serviço referenciado (o serviço no qual você está dependendo) passar no healthcheck. Ou seja, o serviço depende da condição de saúde do serviço referenciado (como o banco de dados estar pronto para aceitar conexões).service_started
O serviço dependente será iniciado assim que o serviço referenciado começar a ser iniciado, independentemente de seu status de saúde. Isso significa que o Docker não espera o serviço estar "saudável", apenas que ele tenha começado a iniciar.
O healthcheck no Docker Compose é uma funcionalidade que permite verificar o estado de saúde de um serviço em execução. Ele define como o Docker deve testar se o serviço está funcionando corretamente, o que ajuda a garantir que o serviço esteja realmente pronto para ser usado antes de outro serviço tentar se conectar a ele.
A principal função do healthcheck é verificar a disponibilidade de um serviço de maneira automatizada. Se o serviço não passar no healthcheck, o Docker marcará esse serviço como não saudável. Isso pode ser útil para implementar estratégias de espera, como no caso de dependências entre serviços no Docker Compose (por exemplo, garantir que um banco de dados esteja pronto antes de iniciar um servidor de aplicação).
O healthcheck tem os seguintes componentes:
test
O comando a ser executado para verificar a saúde do serviço. Pode ser um comando simples ou um script que retorna um código de saída indicando se o serviço está saudável ou não.
Exemplo de comando:["CMD", "curl", "-f", "http://localhost/"]
verifica se o serviço está acessível na porta HTTP local.
Também pode usarCMD-SHELL
, que executa um shell para o comando (útil para comandos mais complexos).interval
O intervalo entre as verificações. O valor padrão é 30s. Esse é o tempo entre cada execução do test.retries
O número de tentativas que podem falhar, se todas falharem o docker considera que o container não está saudável. O valor padrão é 3.timeout
O tempo máximo para o comando de verificação ser executado antes de ser considerado falho. Se o comando não for concluído dentro desse tempo, ele será tratado como uma falha. O valor padrão é 30 segundos.start_period
O tempo após o início do container até o início da verificação de saúde. Durante esse período, o Docker não irá considerar falhas no healthcheck. O valor padrão é 0s.
Âncoras e Referências
No docker-compose.yml
, podemos usar algo chamado âncoras e referências, eles evitam a repetição de configurações semelhantes em diferentes partes do arquivo. Isso faz com que o arquivo fique mais limpo, reutilizável e fácil de manter.
Sem o uso de Âncoras
Em um arquivo docker-compose.yml
simples, onde não usamos âncoras, teríamos que repetir as configurações em cada serviço. Abaixo podemos ver um exemplo de como isso seria feito sem âncoras:
version: '3'
services:
webapp:
image: webapp-image
volumes:
- /path/to/data
- /path/to/config
environment:
- ENV_VAR1=value1
- ENV_VAR2=value2
backend:
image: backend-image
volumes:
- /path/to/data
- /path/to/config
environment:
- ENV_VAR1=value1
- ENV_VAR2=value2
Neste caso, estamos repetindo as mesmas configurações de volumes e variáveis de ambiente para os serviços webapp
e backend
. Isso pode ser problemático em arquivos grandes, pois qualquer alteração nas configurações de volumes ou variáveis de ambiente precisaria ser feita em cada lugar onde essas configurações aparecem.
Com o uso de Âncoras e Referências
Agora, vamos refatorar o arquivo utilizando âncoras e referências para evitar a duplicação de configurações. As âncoras são definidas com &
e podem ser reutilizadas com *
.
version: '3'
x-volumes:
&default-volumes
- /path/to/data
- /path/to/config
x-environment:
&default-environment
- ENV_VAR1=value1
- ENV_VAR2=value2
services:
webapp:
image: webapp-image
volumes:
*default-volumes
environment:
*default-environment
backend:
image: backend-image
volumes:
*default-volumes
environment:
*default-environment
As âncoras &default-volumes
e &default-environment
são definidas sob a chave x-volumes
e x-environment
respectivamente. Elas armazenam os valores de volumes e variáveis de ambiente que serão reutilizados.
Para usar essas configurações em serviços, usamos *default-volumes
e *default-environment
dentro de cada serviço. Agora, se for necessário modificar os volumes ou variáveis de ambiente, basta alterar a definição da âncora no topo do arquivo, e todas as referências serão automaticamente atualizadas.
O prefixo x-
é uma convenção para extensões (ou "aliases") no formato YAML. Esse prefixo indica que a chave não é uma parte padrão do Docker Compose, mas sim uma chave personalizada, usada para armazenar configurações reutilizáveis.
O Compose não interpreta diretamente as chaves com x-
, o que as torna perfeitas para serem usadas para armazenar valores que desejamos referenciar. Podemos criar as âncoras e referências sem o prefixo x-
caso desejado.
dotenv
Quando trabalhamos com docker-compose
, é comum nos depararmos com a necessidade de reutilizar arquivos de configuração em diferentes ambientes, como desenvolvimento local, homologação e produção. Cada um desses ambientes pode exigir portas diferentes, usuários e senhas específicos, caminhos distintos e até nomes de containers personalizados.
Se todas essas informações forem escritas diretamente no arquivo docker-compose.yaml
, além de dificultar a manutenção, isso aumenta o risco de expor dados sensíveis ou de gerar conflitos ao subir os serviços em contextos distintos. Também se torna complicado versionar esse tipo de configuração, pois cada alteração local pode impactar outras pessoas do time ou afetar o comportamento dos containers fora do ambiente original.
Para resolver esse problema, podemos utilizar arquivos .env
, que funcionam como fontes externas de variáveis de ambiente. O docker-compose
lê esse arquivo automaticamente, e todas as variáveis declaradas ali podem ser referenciadas no docker-compose.yaml
usando a sintaxe ${VARIAVEL}
. Isso permite manter o arquivo docker.compose.yaml
limpo, reutilizável e genérico, enquanto os detalhes específicos de cada ambiente ficam isolados no .env
.
Dessa forma, conseguimos trocar informações como senhas, nomes de banco de dados ou números de porta sem alterar o arquivo principal de configuração, bastando apenas atualizar o .env
.
Além disso, é recomendado criar um arquivo .env.example
contendo apenas os nomes das variáveis esperadas, sem os valores reais. Isso facilita o compartilhamento da estrutura com outros desenvolvedores sem comprometer a segurança.
Usemos o compose abaixo como exemplo:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-dev
#restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: dbtest
MYSQL_USER: usuario
MYSQL_PASSWORD: senha123
TZ: America/Sao_Paulo
command:
- --authentication-policy=caching_sha2_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb_force_recovery=0
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Nesse arquivo as senhas e usuários estão "hard coded" no arquivo docker-compose.yaml
, isso quer dizer que para mudar a senha ou o usuário eu teria que alterar diretamente no arquivo. Outro problema é que compartilhar isso com outros desenvolvedores seria um problema.
Para isso vamos usar o .env
, tornando nosso arquivo docker-compose.yaml
mais reutilizavél, podemos ter apenas ele e subir diferentes ambientes como dev, prod, test e etc. Primeiro crie o arquivo .env
no mesmo lugar que está o docker-compose.yaml
:
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=dbtest
MYSQL_USER=usuario
MYSQL_PASSWORD=senha123
TZ=America/Sao_Paulo
Agora vamos reescrever nosso docker-compose.yaml
para usar o .env
:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-dev
#restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: ${TZ}
command:
- --authentication-policy=caching_sha2_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb_force_recovery=0
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Com isso, o docker-compose
vai automaticamente substituir cada ${VARIAVEL}
pelos valores definidos no .env
na hora de subir os serviços. Assim, o arquivo de composição fica mais limpo e seguro, e você pode manter diferentes .env
para cada ambiente, sem precisar duplicar ou editar o docker-compose.yaml
. Também é possível ignorar o .env
no Git usando um .gitignore
, mantendo apenas um .env.example
com as chaves definidas e sem valores, facilitando a colaboração em equipe.
Sempre use dotenv
Utilizar arquivos .env
em projetos com docker-compose
não é apenas uma boa ideia, é algo essencial e praticamente obrigatório se você quiser seguir boas práticas de segurança e manter seu projeto minimamente profissional. Quando senhas, usuários, portas, nomes de bancos e outros dados sensíveis ficam explícitos no docker-compose.yaml
, você corre o risco de expor essas informações acidentalmente em repositórios públicos, ambientes de produção ou até em commits internos que deveriam ser seguros.
Mesmo que o projeto esteja em um repositório privado, isso ainda representa um descuido grave do ponto de vista da segurança e mostra falta de experiência profissional. Separar esses valores em um arquivo .env
permite que você mantenha essas informações fora do versionamento, deixando o arquivo principal limpo, reutilizável e sem risco de vazamento.
Além disso, essa abordagem torna muito mais fácil alternar entre diferentes ambientes, como desenvolvimento, testes e produção, apenas trocando o conteúdo do .env
, sem precisar editar o docker-compose.yaml
o tempo todo.
Ignorar o uso de .env
significa colocar em risco a integridade do seu ambiente, a confidencialidade das informações e ainda comprometer a escalabilidade do projeto em equipe. Por isso, usar dotenv com docker-compose
não é apenas recomendado, é um passo obrigatório para quem quer manter segurança, organização e boas práticas no desenvolvimento de sistemas.
Compose Secrets
O Compose mais recente (especialmente a versão 3.1+ do Docker Compose e no Compose V2) trouxe suporte ao uso de secrets:
mesmo em modo standalone (fora do Swarm). Isso significa que você pode declarar segredos diretamente no docker-compose.yaml
e montá-los como arquivos dentro do container, embora a funcionalidade seja basicamente um bind-mount de um arquivo secreto no host, não envolvendo criptografia dedicada pelo Docker.
Por exemplo:
version: "3.9"
services:
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
secrets:
db_root_password:
file: ./db_root_password.txt
db_password:
file: ./db_password.txt
Com isso, o Compose irá montar os arquivos ./db_root_password.txt
e ./db_password.txt
dentro dos containers, em /run/secrets/...
. Já imagens oficiais como MySQL e Postgres reconhecem as variáveis _FILE
para ler credenciais via arquivo em vez de variável de ambiente.
No entanto, é importante entender que essa aproximação não traz os benefícios de segurança que o Swarm oferece, como criptografia em trânsito e em repouso, controle fino de acesso, armazenamento em memória e etc. pois o sistema apenas cria um bind-mount para o arquivo secreto no host.