Usar docker compose em produção é uma decisão legítima para muitos times de TI, desde que você conheça os limites da ferramenta e aplique um mínimo de disciplina operacional. Neste tutorial, você vai montar uma stack com aplicação e PostgreSQL, adicionar healthchecks, proteger segredos, executar deploys repetíveis e preparar backup. O foco é um único host Linux, o cenário em que o Compose entrega mais valor com menos complexidade.
Quando faz sentido usar Docker Compose em produção
O Compose descreve serviços, redes e volumes em um arquivo declarativo e delega a execução ao Docker Engine. Não existe scheduler multi-nó, autoscaling nativo nem reconciliação contínua como no Kubernetes. Por isso, avalie os trade-offs antes de decidir.
- Vantagens: baixa curva de aprendizado, configuração versionável no Git, paridade entre desenvolvimento e produção, baixo overhead de recursos.
- Desvantagens: ponto único de falha no host, escala horizontal manual, rolling update limitado e sem self-healing além da política de restart.
- Casos de uso concretos: SaaS em estágio inicial, ferramentas internas, ambientes de homologação, painéis de monitoramento e serviços de clientes com carga previsível.
Se você ainda não automatizou a entrega, veja também o Deploy Automatizado com GitHub Actions e Docker: Tutorial Passo a Passo, que complementa este guia no pipeline de CI/CD.
Passo 1: estruture o projeto
Mantenha tudo em um diretório dedicado no servidor, versionado em um repositório de infraestrutura:
- compose.yaml: definição dos serviços.
- .env: variáveis não sensíveis, como tags de imagem e portas.
- secrets/: arquivos de segredo, fora do Git e com permissão restrita (chmod 600).
- backups/: destino temporário de dumps antes do envio para storage externo.
Passo 2: escreva o compose.yaml pensando em produção
O exemplo abaixo usa imagem com tag fixa, política de restart, rede interna, volume nomeado e segredo via arquivo. Evite a tag latest: ela torna o deploy não determinístico e dificulta o rollback.
services:
app:
image: ghcr.io/sua-org/app:1.4.2
restart: unless-stopped
env_file: .env
mem_limit: 512m
depends_on:
db:
condition: service_healthy
ports:
- 127.0.0.1:8080:8080
healthcheck:
test: [CMD-SHELL, wget -qO- http://localhost:8080/health || exit 1]
interval: 15s
timeout: 5s
retries: 3
networks: [backend]
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, pg_isready -U app -d app]
interval: 10s
timeout: 5s
retries: 5
networks: [backend]
volumes:
pgdata:
networks:
backend:
secrets:
db_password:
file: ./secrets/db_password.txtPontos de atenção neste arquivo de Docker Compose: a porta da aplicação é publicada apenas em 127.0.0.1, para que um reverse proxy (Nginx, Caddy ou Traefik) faça a terminação TLS; o banco não expõe porta alguma; e o healthcheck do wget pressupõe que a imagem tenha esse binário, então adapte conforme a sua base.
Passo 3: healthchecks e ordem de inicialização
A diretiva depends_on sozinha só garante a ordem de criação dos containers, não a prontidão do serviço. Com condition: service_healthy, o docker compose aguarda o healthcheck do PostgreSQL passar antes de iniciar a aplicação. Isso evita falhas de conexão no boot, mas não substitui retry com backoff no código: o banco pode cair depois da inicialização.
Passo 4: segredos e variáveis de ambiente
- Prefira segredos em arquivo (_FILE, quando a imagem suporta) em vez de variáveis de ambiente, que aparecem em docker inspect.
- Nunca versione o .env com credenciais; mantenha um .env.example com valores fictícios.
- Valide a configuração final com docker compose config, que mostra o YAML já interpolado e acusa erros de sintaxe.
Os segredos de arquivo do Compose são montados em /run/secrets, mas, fora do Swarm, são um bind mount com as permissões do arquivo de origem. Trate-os como proteção contra vazamento acidental, não como cofre. Para exigências maiores, integre um gerenciador de segredos externo.
Passo 5: deploy repetível e rollback
Um deploy simples e previsível segue três comandos:
docker compose pull
docker compose up -d --remove-orphans
docker compose psO up -d recria apenas os serviços cuja imagem ou configuração mudou. Há uma breve indisponibilidade da aplicação durante a troca do container; se isso for inaceitável, use um proxy com dois upstreams e alterne entre eles (estratégia blue/green manual) ou considere um orquestrador. Para rollback, basta voltar a tag anterior no .env e repetir o up -d, motivo pelo qual as tags imutáveis são essenciais. Automatize esse fluxo no pipeline e registre o commit e a tag de cada release.
Passo 6: backup, logs e observabilidade
Volumes nomeados persistem dados, mas não são backup. Agende um dump lógico com pg_dump via cron ou timer do systemd, envie o arquivo para um storage externo e teste a restauração periodicamente. Um backup nunca restaurado é apenas uma hipótese.
Quanto aos logs, o driver padrão json-file cresce sem limite. Configure rotação por serviço com logging.options (max-size e max-file) ou envie a saída para um coletor central. Para métricas, adicione cAdvisor e um exporter do PostgreSQL ao docker compose, ou use o Prometheus que você já opera. Se a preocupação é custo de infraestrutura, o artigo Custo de Cloud: como reduzir até 70% com DevOps na prática mostra como combinar visibilidade e rightsizing.
Limites que você precisa respeitar
- Alta disponibilidade: um host significa um ponto único de falha. Mitigue com snapshots, backups e automação para recriar o servidor.
- Escala: aumentar réplicas com --scale funciona, mas exige balanceamento e cuidado com portas e estado.
- Segurança: mantenha o Docker Engine atualizado, evite containers privilegiados e rode processos como usuário não root.
- Crescimento: quando precisar de múltiplos nós, rolling updates reais e autoscaling, migre para Kubernetes ou outro orquestrador. O arquivo Compose serve como base para essa conversão.
Conclusão
O Docker Compose em produção funciona bem quando você fixa versões, usa healthchecks, protege segredos, automatiza o deploy e testa backups. Ele não resolve alta disponibilidade, mas entrega um caminho simples, auditável e barato para equipes que querem previsibilidade. Comece pela stack deste tutorial, meça o comportamento real e só adicione complexidade quando a carga ou o SLA exigirem.