Docker Compose com vários serviços: inicie o ambiente de desenvolvimento local com um único comando

Na tarde do meu primeiro dia de trabalho, eu encarava a quinta mensagem de erro na tela: a porta do MySQL estava ocupada, a versão do Redis era incompatível e o RabbitMQ simplesmente não conectava. Um colega mais experiente olhou para a minha tela e suspirou: “Há quanto tempo você está instalando tudo isso?”
“Desde as 9 da manhã”, respondi em voz baixa.
Quatro horas. Levei quatro horas para instalar três bancos de dados. E aquilo era apenas o começo: ainda faltavam ElasticSearch e MongoDB.
Foi quando percebi pela primeira vez que preparar um ambiente de desenvolvimento local era um problema enorme, daqueles que parecem não ter fim.
Mais tarde, nossa equipe adotou o Docker Compose. Um novo integrante clonava o repositório, executava docker-compose up -d e, em 5 minutos, todos os serviços estavam ativos. MySQL, Redis, RabbitMQ, API e Web: um único comando e tudo organizado. Para trocar de projeto, bastava mudar de diretório e iniciar outro arquivo Compose. Para limpar, docker-compose down -v removia inclusive os volumes, sem deixar resíduos.
É essa mudança que quero compartilhar neste artigo: como usar o Docker Compose para orquestrar vários serviços e transformar o ambiente de desenvolvimento local de um pesadelo em algo resolvido com um único comando.
Por que orquestrar vários serviços
Para falar a verdade, dez anos atrás, quando trabalhávamos com aplicações monolíticas, configurar o ambiente era muito simples: instalar o JDK, definir a string de conexão com o banco de dados e executar o projeto. Hoje é diferente.
A maioria dos projetos foi dividida em uma arquitetura de microsserviços ou, no mínimo, separa frontend e backend. Um ambiente de desenvolvimento local típico precisa de pelo menos estes componentes: um serviço Web para o frontend, uma API para o backend, MySQL para os dados de negócio, Redis para cache e sessões e RabbitMQ para mensagens assíncronas. Alguns projetos também usam ElasticSearch para busca e MongoDB para armazenar logs.
É aí que começam os problemas.
O sofrimento da instalação manual
É preciso instalar tudo em cada máquina. Qual versão do MySQL escolher: 5.7 ou 8.0? Se a versão estiver errada, a sintaxe SQL pode ser incompatível. No Redis, é preciso configurar a porta: a padrão é 6379, mas e se outro serviço já estiver usando essa porta? O RabbitMQ ainda exige a instalação do ambiente de execução Erlang. Só o tutorial de instalação do RabbitMQ me tomou meia hora.
Depois da instalação, ainda há conflitos de versão. Um projeto anterior pode ter deixado dados de uma versão antiga do MySQL na máquina. Uma porta entra em conflito e o serviço não inicia. Depois de muito investigar, você descobre que havia um processo zumbi que não foi encerrado corretamente.
O pior é trocar de projeto. Você acaba o projeto A e precisa passar para o projeto B. O MySQL do projeto A está usando a porta 3306, e o projeto B também quer usar 3306. É preciso alterar o arquivo de configuração ou parar os serviços do projeto A. Dois dias depois, você volta ao projeto A e precisa desfazer as alterações.
É um trabalho repetitivo e desgastante.
O pesadelo da colaboração em equipe
“Na minha máquina funciona.”
Toda equipe já ouviu essa frase. Um novo integrante clona o código, instala o ambiente e o projeto não funciona. Por quê? O projeto antigo usava MySQL 5.7, mas a pessoa instalou o 8.0; o Redis do projeto não tinha senha, mas o Redis local foi configurado com senha. Ela altera vários trechos dos arquivos de configuração e, mesmo assim, nada funciona.
Por fim, um colega mais experiente precisa ajudar. Os dois passam mais de meio dia até fazer o ambiente funcionar.
E assim um dia inteiro é desperdiçado.
A solução proposta pelo Compose
A ideia do Docker Compose é simples: empacotar todos os serviços em contêineres e gerenciá-los em conjunto por meio de um único arquivo de configuração.
Você não precisa se preocupar em instalar cada banco de dados, configurar versões ou distribuir portas. Tudo fica definido no arquivo Compose. Um comando inicia os serviços exatamente conforme a configuração. Para trocar de projeto, mude de diretório e execute outro arquivo Compose. Para limpar, um comando remove todos os contêineres e volumes.
É como trocar a tarefa de montar um computador peça por peça pela compra de uma máquina pronta. Você não precisa descobrir como encaixar a memória nem como conectar os cabos de energia da placa de vídeo: o fabricante já fez isso, e basta ligar o equipamento.
Configuração essencial do docker-compose.yml
Comecemos por um arquivo de configuração completo. Imagine que o projeto tenha quatro serviços: frontend Web, backend API, banco de dados MySQL e cache Redis.
# docker-compose.yml
version: "3.8" # versão do arquivo Compose; 3.8 aceita a maioria das opções de configuração
services:
# serviço Web do frontend
web:
build: ./frontend # cria a imagem a partir do diretório local frontend
ports:
- "3000:3000" # porta 3000 do host -> porta 3000 do contêiner
depends_on:
- api # depende do serviço api, que inicia primeiro
environment:
- API_URL=http://api:8080 # endereço usado pelo frontend para acessar o backend
# serviço API do backend
api:
build: ./backend # cria a imagem a partir do diretório local backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis # depende do banco de dados e do cache
environment:
- DB_HOST=mysql # endereço do banco de dados (nome do contêiner)
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=dev123 # senha do ambiente de desenvolvimento; em produção, use um arquivo .env
- REDIS_HOST=redis
- REDIS_PORT=6379
# banco de dados MySQL
mysql:
image: mysql:8.0 # usa diretamente a imagem oficial, sem construir outra
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=dev123
- MYSQL_DATABASE=myapp # cria o banco de dados automaticamente
volumes:
- mysql_data:/var/lib/mysql # persiste os dados no volume
# cache Redis
redis:
image: redis:7-alpine # a versão alpine é menor
ports:
- "6379:6379"
volumes:
mysql_data: # define o volume usado para persistir os dados do MySQL
Explicação dos principais campos
Em services, você define todos os serviços. Cada um pode ter três origens: build cria uma imagem a partir do código local, image baixa diretamente uma imagem oficial ou você pode combinar os dois, construindo localmente com base em uma imagem específica.
ports define o mapeamento de portas. O formato é "porta do host:porta do contêiner". Na configuração acima, a Web usa 3000, a API usa 8080, o MySQL usa 3306 e o Redis usa 6379. Se uma porta local estiver ocupada, altere a porta do host. Por exemplo, com "13006:3306", você se conecta ao MySQL por localhost:13006.
depends_on controla a ordem de inicialização. MySQL e Redis iniciam primeiro, depois a API, que depende deles, e por último a Web, que depende da API. Mas há uma armadilha que veremos adiante.
environment define variáveis de ambiente. Senhas do banco de dados, endereços de conexão e portas podem ser configurados aqui. Em produção, não coloque senhas diretamente nesse campo; use um arquivo .env ou injete variáveis de ambiente.
volumes garante a persistência dos dados. Os dados do MySQL são armazenados no volume mysql_data, portanto não são perdidos quando o contêiner é removido. Na próxima inicialização, eles continuarão disponíveis.
Uma armadilha comum
O nome do contêiner corresponde ao nome do serviço. Na configuração acima, a API se conecta ao banco de dados usando DB_HOST=mysql, e não localhost. Por quê?
Cada contêiner tem seu próprio ambiente de rede. Dentro do contêiner da API, localhost aponta para o próprio contêiner da API, não para a máquina host nem para o contêiner do MySQL. O Compose cria automaticamente uma rede interna, e os serviços usam seus nomes para se comunicar. O nome mysql é o endereço do contêiner do MySQL nessa rede.
Na primeira vez que escrevi um arquivo Compose, caí nessa armadilha: usei localhost:3306 e não consegui me conectar ao banco de dados. Só depois descobri que precisava usar o nome do contêiner.
Dependências e ordem de inicialização dos serviços
O depends_on parece simples: o MySQL inicia primeiro e a API depois. Na prática, há uma questão sutil.
O depends_on do Compose garante apenas a ordem de inicialização dos contêineres, não a ordem em que os serviços ficam prontos. Em outras palavras, o contêiner do MySQL pode ter iniciado enquanto o serviço MySQL ainda não aceita conexões, pois está inicializando o banco, carregando configurações ou abrindo a porta de escuta. Se a API tentar se conectar nesse momento, provavelmente falhará.
Já passei por isso. Logo depois de executar docker-compose up, a API apresentou um erro de conexão com o banco de dados. Dez segundos depois, tentei novamente e funcionou. O contêiner do MySQL estava ativo, mas o serviço ainda não estava pronto.
Solução 1: verificação de integridade
O Compose permite adicionar uma verificação de integridade à configuração. O serviço dependente só inicia depois que essa verificação é aprovada.
services:
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s # verifica a cada 5 segundos
timeout: 3s # tempo limite
retries: 10 # considera unhealthy somente depois de 10 falhas
# ... outras configurações
api:
depends_on:
mysql:
condition: service_healthy # aguarda a aprovação da verificação de integridade do MySQL
O MySQL executa mysqladmin ping para verificar se consegue aceitar conexões. A verificação ocorre a cada 5 segundos, com no máximo 10 tentativas, totalizando 50 segundos. A API só inicia quando a verificação é aprovada.
Esse método funciona, mas tem uma desvantagem: você precisa escrever uma configuração de integridade para cada serviço. Algumas imagens oficiais, como a do Redis, não oferecem um comando conveniente para isso, então é necessário criar outra solução.
Solução 2: tentativas na aplicação
Uma opção mais prática é implementar tentativas na própria aplicação. Se a conexão falhar, aguarde alguns segundos e tente novamente. Se o MySQL demorar para iniciar, basta esperar até que esteja pronto.
Uma estratégia comum é o backoff exponencial: aguarde 1 segundo na primeira tentativa, 2 segundos na segunda, 4 segundos na terceira e aumente o intervalo gradualmente. A maioria dos bancos de dados fica pronta em até 30 segundos.
Em Node.js, você pode usar a configuração do pool de conexões do mysql2:
const pool = mysql.createPool({
host: 'mysql',
port: 3306,
user: 'root',
password: 'dev123',
database: 'myapp',
waitForConnections: true, // aguarda uma conexão ficar disponível
connectionLimit: 10,
queueLimit: 0,
});
Em Python, use a biblioteca tenacity para implementar as tentativas:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def connect_db():
return mysql.connector.connect(host='mysql', ...)
Como escolher entre as duas soluções
A verificação de integridade é mais precisa: a API só inicia quando o banco de dados está realmente pronto. Porém, a configuração é um pouco mais trabalhosa, pois cada banco precisa de um comando de verificação específico.
As tentativas na aplicação são mais simples: bastam algumas linhas de código, sem alterar a configuração do Compose. A desvantagem é que, depois de iniciar, a API pode passar algum tempo repetindo as tentativas, e os logs mostrarão erros de conexão, embora isso não afete o resultado final.
Eu prefiro as tentativas na aplicação porque dão menos trabalho e funcionam bem na maioria dos casos. Deixo a verificação de integridade como alternativa para serviços que demoram especialmente para iniciar.
Estratégia de configuração para vários ambientes
As configurações de desenvolvimento local, teste e produção costumam ser diferentes. Um exemplo é o mapeamento de portas: no desenvolvimento, é útil expor a porta do banco de dados para depurar localmente; em produção, isso não é necessário, pois o banco deve ser acessado apenas pela rede interna dos contêineres.
Se todas essas configurações estiverem no mesmo arquivo, trocar de ambiente exige editar o conteúdo e depois desfazer as mudanças. Além de inconveniente, isso aumenta o risco de erro.
O Compose resolve esse problema com um arquivo base e arquivos de sobreposição.
Arquivo base: configuração compartilhada
O docker-compose.yml contém as configurações compartilhadas por todos os ambientes: definição dos serviços, versões das imagens, rede interna e volumes de dados.
# docker-compose.yml (configuração base)
version: "3.8"
services:
web:
build: ./frontend
# ports não é definido aqui; o arquivo de sobreposição o complementa
api:
build: ./backend
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
# ports não é definido aqui
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
# ports não é definido aqui; em produção, não é preciso expor a porta
redis:
image: redis:7-alpine
volumes:
mysql_data:
O mapeamento de portas não aparece porque a configuração varia entre ambientes.
Arquivo de sobreposição para desenvolvimento
O docker-compose.override.yml acrescenta as configurações específicas do ambiente de desenvolvimento: mapeamento de portas, senha de desenvolvimento e variáveis de ambiente usadas na depuração.
# docker-compose.override.yml (ambiente de desenvolvimento)
version: "3.8"
services:
web:
ports:
- "3000:3000" # expõe a porta no desenvolvimento para facilitar o acesso local
api:
ports:
- "8080:8080"
environment:
- DEBUG=true # ativa o modo de depuração no ambiente de desenvolvimento
mysql:
ports:
- "3306:3306" # expõe a porta do banco no desenvolvimento para facilitar a depuração
environment:
- MYSQL_ROOT_PASSWORD=dev123 # senha simples para o ambiente de desenvolvimento
redis:
ports:
- "6379:6379"
O Compose tem um comportamento padrão: ao executar docker-compose up, combina automaticamente docker-compose.yml e docker-compose.override.yml. As configurações dos dois arquivos são mescladas, e o override prevalece sobre a configuração base.
Por isso, no desenvolvimento local, basta executar docker-compose up para obter a configuração completa do ambiente.
Arquivo de sobreposição para produção
O docker-compose.prod.yml acrescenta as configurações de produção: não expõe portas, usa senhas de produção e se conecta a serviços externos.
# docker-compose.prod.yml (ambiente de produção)
version: "3.8"
services:
web:
# não expõe a porta; o acesso é feito por um proxy reverso (nginx)
api:
environment:
- DB_HOST={{DB_HOST}} # lê a variável de ambiente, sem gravar a senha no arquivo
- DB_PASSWORD={{DB_PASSWORD}}
mysql:
# não expõe a porta, impedindo conexões externas diretas
environment:
- MYSQL_ROOT_PASSWORD={{MYSQL_ROOT_PASSWORD}}
No ambiente de produção, use o parâmetro -f para especificar os arquivos de configuração:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Os dois arquivos são combinados, e a configuração de produção prevalece sobre a base. A porta do banco de dados não fica exposta, e a senha é lida das variáveis de ambiente.
Injeção de variáveis de ambiente
Senhas de produção não devem ser gravadas no arquivo. O Compose pode ler os valores de um arquivo .env ou das variáveis de ambiente do sistema.
# arquivo .env (não envie para o Git)
DB_HOST=prod-mysql.internal
DB_PASSWORD=super_secret_password_123
MYSQL_ROOT_PASSWORD=another_secret
No arquivo de configuração, use a sintaxe {{VAR:-default}} para referenciar os valores:
environment:
- DB_HOST={{DB_HOST:-localhost}} # se DB_HOST não estiver definida, usa localhost
- DB_PASSWORD={{DB_PASSWORD:-dev123}}
Não envie o arquivo .env ao controle de versão. Crie um .env.example com valores de exemplo para que cada integrante da equipe possa copiá-lo e preencher sua própria configuração.
Comandos práticos para controlar tudo
Com a configuração pronta, é hora de iniciar, parar e depurar. Estes comandos cobrem praticamente todas as operações do dia a dia.
Iniciar todos os serviços
docker-compose up -d
up inicia todos os serviços. -d significa execução em segundo plano, ou detached mode, e deixa o terminal livre. Sem -d, os logs de todos os serviços são exibidos diretamente no terminal, e Ctrl+C os interrompe.
Durante a inicialização, o Compose baixa imagens quando você usa image, constrói imagens quando usa build, cria os contêineres e inicia os serviços. A primeira execução é mais lenta por causa do download das imagens. Nas próximas, ela será mais rápida porque as imagens já estarão disponíveis.
Ver o status dos serviços
docker-compose ps
Esse comando lista o estado de todos os contêineres. A saída é semelhante a esta:
NAME COMMAND SERVICE STATUS PORTS
myapp-web-1 "npm start" web running 0.0.0.0:3000->3000/tcp
myapp-api-1 "node index.js" api running 0.0.0.0:8080->8080/tcp
myapp-mysql-1 "mysqld" mysql running 0.0.0.0:3306->3306/tcp
myapp-redis-1 "redis-server" redis running 0.0.0.0:6379->6379/tcp
Quando STATUS mostra running, o serviço está funcionando. Se aparecer exited ou error, a inicialização falhou.
Ver os logs de um serviço
docker-compose logs -f api
Esse comando mostra os logs do serviço de API. -f acompanha continuamente a saída, exibindo novos registros em tempo real. Sem -f, apenas os logs existentes são mostrados.
Sem informar o nome do serviço, docker-compose logs -f exibe os logs de todos os serviços, mas a saída pode ficar confusa quando o volume é grande.
Parar e limpar
docker-compose down
Esse comando para todos os contêineres e remove contêineres e redes. Os volumes de dados permanecem, portanto os dados do MySQL continuam disponíveis.
Para fazer uma limpeza completa, incluindo os volumes:
docker-compose down -v
-v remove os volumes. Na próxima inicialização, o MySQL será inicializado novamente e todos os dados terão sido apagados. Esse comando é usado com frequência durante a depuração: quando os dados ficam inconsistentes, você os apaga e recomeça.
Reconstruir uma imagem
Depois de alterar o código, reconstrua a imagem:
docker-compose build api
Esse comando constrói apenas a imagem do serviço de API. Depois, reinicie o contêiner:
docker-compose up -d api
Também é possível construir e reiniciar em uma única etapa:
docker-compose up -d --build api
--build força a reconstrução da imagem, mesmo quando ela já existe.
Tabela de referência dos comandos mais usados
| Comando | Função |
|---|---|
docker-compose up -d | Inicia todos os serviços em segundo plano |
docker-compose ps | Mostra o status dos serviços |
docker-compose logs -f api | Mostra os logs da API |
docker-compose down | Para e remove os contêineres |
docker-compose down -v | Para e remove contêineres e volumes de dados |
docker-compose restart api | Reinicia o serviço de API |
docker-compose build api | Reconstrói a imagem da API |
Esses comandos cobrem 90% das operações diárias. Consulte a documentação quando precisar de outros comandos, como exec, cp ou top.
Conclusão
Compare a eficiência da abordagem tradicional com a do Compose:
| Operação | Abordagem tradicional | Abordagem com Compose |
|---|---|---|
| Preparar o ambiente para um novo integrante | 4 a 8 horas | 5 minutos (clone + up) |
| Trocar de projeto | Alterar configurações, parar serviços e reiniciar | Mudar de diretório e iniciar outro compose |
| Limpar o ambiente | Desinstalar manualmente e procurar processos residuais | Remover contêineres e volumes com um comando |
| Manter ambientes consistentes na equipe | Cada máquina pode ser diferente | Um único arquivo de configuração garante ambientes iguais |
A diferença é evidente.
Se você ainda instala bancos de dados manualmente, altera arquivos de configuração e investiga conflitos de portas, experimente o Docker Compose. Comece com um projeto simples: uma API e um MySQL. Escreva um docker-compose.yml e execute-o. Quando dominar o processo, acrescente Redis e RabbitMQ e crie configurações para vários ambientes.
Para adotar a solução na equipe, envie docker-compose.yml e docker-compose.override.yml ao repositório e acrescente ao README as etapas de inicialização. Assim, um novo integrante clona o código, executa um único comando e tem o ambiente de desenvolvimento pronto.
Isso é muito mais confiável do que escrever um “manual de configuração do ambiente”. A documentação fica desatualizada; o arquivo de configuração, não.
Orquestração prática de vários serviços com Docker Compose
Use o Docker Compose para orquestrar quatro serviços — Web, API, MySQL e Redis — e iniciar o ambiente de desenvolvimento local com um único comando
⏱️ Estimated time: 15 min
- 1
Step 1: Crie o arquivo docker-compose.yml
Crie o arquivo de configuração na raiz do projeto:
```yaml
version: "3.8"
services:
web:
build: ./frontend
ports: ["3000:3000"]
depends_on: [api]
api:
build: ./backend
ports: ["8080:8080"]
depends_on: [mysql, redis]
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: dev123
MYSQL_DATABASE: myapp
redis:
image: redis:7-alpine
```
Observação: para a comunicação entre contêineres, use o nome do serviço, como DB_HOST=mysql, e não localhost - 2
Step 2: Inicie todos os serviços
Execute no diretório que contém o docker-compose.yml:
```bash
docker-compose up -d
```
• Na primeira execução, o download das imagens pode demorar
• Nas próximas execuções, as imagens existentes serão usadas e a inicialização levará apenas alguns segundos
• Sem -d, o terminal ficará ocupado exibindo os logs - 3
Step 3: Verifique o status dos serviços
Confirme se todos os contêineres foram iniciados corretamente:
```bash
docker-compose ps
```
• STATUS com running indica funcionamento normal
• Se aparecer exited ou error, investigue com logs:
```bash
docker-compose logs api
``` - 4
Step 4: Configure vários ambientes (opcional)
Crie o docker-compose.override.yml para o ambiente de desenvolvimento:
```yaml
version: "3.8"
services:
mysql:
ports: ["3306:3306"]
```
• O Compose combina automaticamente o arquivo override
• No ambiente de produção, especifique os arquivos com -f:
```bash
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
``` - 5
Step 5: Limpe o ambiente
Pare e remova todos os contêineres:
```bash
docker-compose down # mantém os volumes de dados
docker-compose down -v # remove os volumes e apaga os dados
```
• Ao trocar de projeto, use down para fazer a limpeza
• Se os dados estiverem inconsistentes e você quiser recomeçar, use down -v
FAQ
Qual é a diferença entre docker-compose.yml e Dockerfile?
O depends_on garante que o serviço esteja pronto?
Como acessar um serviço da máquina host a partir de um contêiner?
Onde os dados ficam armazenados? Eles são perdidos quando o contêiner é removido?
Como gerenciar configurações para vários ambientes, como dev, test e prod?
O que fazer quando uma porta já está em uso?
15 min de leitura · Publicado em: 9 abr 2026 · Atualizado em: 4 set 2026
Guia prático Docker
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Como acelerar builds do Docker com cache: guia prático
Aprenda a usar o cache de camadas do Docker, configurar o .dockerignore e otimizar o Dockerfile para reduzir builds de 10 minutos para 30 segundos, com exemplos de cache do BuildKit.
Parte 7 de 34
Próximo
Dependências no Docker Compose: healthcheck para resolver a ordem de inicialização do banco
Entenda como configurar depends_on e healthcheck no Docker Compose para evitar falhas quando a aplicação inicia antes do banco de dados, com exemplos completos para PostgreSQL e MySQL.
Parte 9 de 34



Comentários
Entre com GitHub para comentar