O problema que o Compose resolve
Uma aplicação real quase nunca é um container só. Uma API web precisa de um banco de dados, o banco precisa de um volume para os dados sobreviverem a um reinício, e depois você adiciona um cache Redis e um worker em segundo plano. São quatro comandos docker run, cada um com suas flags de portas, volumes, variáveis de ambiente e uma rede compartilhada. Na ordem certa. Toda vez que você senta para trabalhar.
O Docker Compose troca esses comandos por um arquivo e um comando. Você descreve os containers e como eles se conectam em um arquivo chamado compose.yaml, e docker compose up sobe todos. Se você ainda não mexeu com imagens e docker run, comece por o que é Docker e como os containers funcionam.
O que o arquivo compose descreve
Um arquivo compose tem um punhado de chaves de primeiro nível. A que você sempre usa é services: cada serviço é um container que o Compose vai executar.
| |
Dois containers. api é construído a partir do Dockerfile do diretório atual e publica a porta 8000. db executa a imagem oficial postgres:18 e guarda os dados em um volume nomeado, então os dados continuam lá quando o container é recriado.
Dois detalhes fazem o trabalho de verdade aqui:
apialcançadbpelo hostnamedb. O Compose coloca cada serviço em uma rede compartilhada e registra o nome de cada serviço como nome DNS. Sem endereços IP, sem o antigo--link.DATABASE_URLaponta paradb:5432e o Compose resolve o nome.depends_oncomcondition: service_healthyseguraapiaté o Postgres responder de verdade. Umdepends_onsimples só espera o container iniciar, não o processo do banco lá dentro aceitar conexões: essa diferença é a causa mais comum de um “connection refused” no primeiro boot.
As três chaves de primeiro nível: services, volumes, networks
Três chaves de primeiro nível correspondem a conceitos de Docker que você já conhece:
| Chave | O que define | Equivalente manual |
|---|---|---|
services | os containers a executar | docker run |
volumes | volumes nomeados para dados que precisam persistir | docker volume create |
networks | redes entre serviços | docker network create |
Você raramente declara networks. O Compose cria uma rede por projeto e conecta cada serviço a ela, e é por isso que o exemplo com Postgres não precisou de nenhuma configuração de rede. Declare redes explicitamente só quando quiser impedir que grupos de serviços se enxerguem:
| |
Os comandos que você vai usar de verdade
Rode estes comandos a partir do diretório que contém o compose.yaml.
| |
up e down são os comandos que você mais digita. Dá para rodar up de novo quantas vezes quiser: depois de editar o arquivo, ele recria só os serviços cuja configuração mudou e não mexe no resto.
O que docker compose up imprime no primeiro boot
Execute o exemplo com Postgres acima e você recebe algo assim:
| |
O Compose põe o nome do projeto na frente de cada nome (por padrão, o nome do diretório) e um número no fim, porque ele já está preparado para rodar mais de uma réplica do mesmo serviço. app-db-1 fica Healthy antes de app-api-1 subir — é o condition: service_healthy fazendo o trabalho dele.
Como manter as senhas fora do arquivo compose
O Compose lê um arquivo chamado .env no diretório do projeto e substitui as referências ${VAR} no arquivo compose:
| |
| |
Mantenha o .env fora do controle de versão e, no lugar dele, versione um .env.example com valores vazios ou fictícios. Para algo realmente sensível num ambiente em produção, use Docker secrets em vez de variáveis de ambiente.
Serviços opcionais com profiles
Nem todo serviço precisa subir toda vez. Uma entrada profiles mantém um serviço inativo até você pedir:
| |
docker compose up sobe api e db e ignora seed por completo. docker compose --profile tools run --rm seed executa o seeder quando você pede. Use profiles para tudo que é pontual: seeders, migrações, um shell de depuração, um gerador de carga que você só quer durante um teste.
Compose v2 vs o antigo docker-compose
Se um guia manda você executar docker-compose com hífen, ele é anterior a 2023. Aquela era a v1, escrita em Python, hoje sem suporte. A ferramenta atual é docker compose, um subcomando da CLI do Docker; já vem no Docker Desktop e no pacote do plugin Compose para o Docker Engine.
Arquivos antigos costumam trazer esta linha no topo:
| |
A chave version não tem efeito nenhum no Compose v2. O Compose valida contra a Compose Specification atual e avisa quando a chave está presente. Remova e o aviso some.
O nome padrão do arquivo também mudou: compose.yaml é o atual, docker-compose.yml ainda funciona, e o Compose procura pelos dois.
Live reload durante o desenvolvimento
docker compose watch (Compose 2.22 em diante) atualiza os containers enquanto você edita. Adicione um bloco develop ao serviço:
| |
sync copia os arquivos alterados direto para o container em execução, então uma mudança de código aparece sem rebuild. rebuild dispara um rebuild completo da imagem quando muda um arquivo que afeta o build, como um lockfile de dependências. Inicie com docker compose watch, ou adicione --watch ao up.
Quando o Compose é a ferramenta errada
O Compose executa containers em uma máquina só. Esse é o limite, e a maior parte do que falta no Compose está do outro lado dele.
Ele serve para desenvolvimento local, testes automatizados em CI e deploys pequenos em um único host. Ele não sabe distribuir containers por vários servidores, substituir um quando um nó cai, fazer rollouts graduais condicionados aos healthchecks, nem autoescalar.
Isso é trabalho de um orquestrador: Kubernetes, ou Docker Swarm se você quer algo mais leve. Os dois não competem. Um arquivo compose costuma ser o rascunho que depois vira um conjunto de manifests do Kubernetes, e muitos times seguem usando o Compose localmente bem depois de a produção ter migrado para um cluster.
Quem usa Podman também tem um caminho compatível: podman compose e podman-compose leem o mesmo formato de arquivo, com as ressalvas cobertas em Docker vs Podman.
Erros comuns
- Segredos commitados no
compose.yaml. O arquivo vai para o Git. Use.env(no gitignore) ou Docker secrets. - Confiar no
depends_onpara esperar um serviço ficar pronto. Sozinho, ele espera o container iniciar, não o serviço aceitar conexões. Combine com umhealthcheckecondition: service_healthy. - Definir
container_name. Isso impede você de rodar mais de uma cópia do projeto e quebradocker compose up --scale. Deixe o Compose nomear os containers. - Fazer bind mount por cima de um diretório de dependências instaladas. Montar a pasta do projeto num container Node ou Python pode mascarar o
node_modulesou o virtualenv criado durante o build. Monte subpastas do código, ou coloque um volume anônimo no caminho das dependências.
Como passar uma app existente para o Compose
Pegue a app que você hoje inicia com um script cheio de linhas docker run e mova para um compose.yaml um serviço de cada vez. Suba, leia os logs, conserte o que quebrar, adicione o serviço seguinte. Dê um healthcheck a tudo de que outros serviços dependem: é o passo que todo mundo pula, e é o que corta o “connection refused” num boot a frio.
Você vai saber que deu certo quando docker compose down -v && docker compose up reconstruir o ambiente inteiro do zero e a app voltar igual todas as vezes.