Docker Compose
Docker Compose descreve, num arquivo YAML só, todos os serviços que compõem uma aplicação — e
como eles se conectam — em vez de subir cada container manualmente com uma sequência longa de
flags docker run. Os exemplos abaixo são ilustrativos, com nomes de serviço, rede, domínio e
porta genéricos — o valor de cada um é ajustado por projeto.
Compose do front (Next.js)
services:
app-front:
build:
context: .
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.example.com}
container_name: app-front
restart: unless-stopped
ports:
- "3000:3000"
networks:
- app_net
networks:
app_net:
external: true
Compose da API (Rails) + banco
services:
app-api:
build: .
container_name: app-api
restart: unless-stopped
env_file:
- .env
ports:
- "3001:80"
depends_on:
- app-api-db
networks:
- app_net
volumes:
- app_api_storage:/rails/storage # uploads (ActiveStorage) sobrevivem a rebuilds
app-api-db:
image: postgres:15
container_name: app-api-db
restart: unless-stopped
environment:
POSTGRES_USER: app_api
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?defina POSTGRES_PASSWORD no .env}
POSTGRES_DB: app_api_production
volumes:
- app_api_db_data:/var/lib/postgresql/data
networks:
- app_net
volumes:
app_api_db_data:
app_api_storage:
networks:
app_net:
external: true
Build-time vs. runtime — a distinção que mais gera confusão
NEXT_PUBLIC_* do Next.js é embutido no bundle JavaScript durante o build, não lido em
runtime — é por isso que ele aparece em build.args (que vira ARG dentro do Dockerfile, só
disponível durante docker build), não em environment/env_file (que só afetam o container já
rodando). Mudar uma variável NEXT_PUBLIC_* no .env de produção sem reconstruir a imagem
não tem efeito nenhum — o valor antigo já está compilado dentro do JavaScript servido. Isso
surpreende quem vem de uma stack onde toda variável de ambiente é lida em runtime (como o Rails
puro): em Next.js, a pergunta "essa env var é lida em build ou em runtime?" muda completamente
onde ela precisa ser declarada no Compose.
restart: unless-stopped
Reinicia o container automaticamente se ele cair (crash, erro não tratado, OOM — ver
Limites de recursos e OOM Killer)
ou se o Docker/a VPS reiniciar — mas não força restart se alguém parou o container
manualmente (docker stop), diferente de restart: always. É o padrão sensato pra serviço de
produção: recupera de falha sozinho, sem brigar com uma parada intencional.
depends_on — ordem de start, não prontidão
depends_on: [app-api-db] garante que o container do banco inicia antes do da API — não
garante que o Postgres já está pronto pra aceitar conexão nesse momento (o processo do
Postgres pode levar alguns segundos pra ficar disponível depois de iniciar). Uma aplicação que não
tolera essa corrida (falha na primeira tentativa de conexão em vez de tentar de novo) precisa de
retry próprio na inicialização, ou de uma checagem de saúde (healthcheck + condition: service_healthy) explícita no Compose.
Volume nomeado — o que sobrevive a um rebuild
Por padrão, tudo que um container escreve no próprio sistema de arquivos desaparece quando ele é
recriado (docker compose up --build). Um volume nomeado (app_api_storage,
app_api_db_data acima) é montado num caminho específico e persiste independente do ciclo de
vida do container — essencial pra dado do banco e pra upload de usuário (ActiveStorage,
por exemplo), que não podem sumir a cada deploy.
env_file — segredo fora da imagem
env_file: [.env] injeta variável de ambiente no container sem colocá-la no Dockerfile nem
na imagem final — a imagem gerada não carrega segredo nenhum embutido, e o mesmo .env (fora do
controle de versão) muda por ambiente sem precisar rebuild.
Rede externa (external: true)
Uma rede Docker external: true referencia uma rede já criada fora desse docker-compose.yml
(via docker network create), em vez de o Compose criar (e destruir) a rede junto com os
serviços. Padrão útil quando múltiplos docker-compose.yml — de projetos diferentes, subidos
separadamente — precisam compartilhar a mesma rede pra se enxergarem por nome (ver
VPS e containers); sem isso, cada
docker compose up criaria sua própria rede isolada, e serviços de arquivos Compose diferentes
não se comunicariam.
${VAR:?mensagem} — variável obrigatória, com erro explícito
A sintaxe ${POSTGRES_PASSWORD:?defina POSTGRES_PASSWORD no .env} faz o Compose recusar subir
se essa variável não estiver definida, mostrando a mensagem de erro escolhida — em vez de subir
mesmo assim com um valor vazio (o que aconteceria com ${POSTGRES_PASSWORD} sozinho, criando um
banco com senha vazia silenciosamente). Prefira essa forma pra toda variável sem a qual o serviço
não deveria nem tentar iniciar.
Leitura de apoio
- Docker Compose — documentação oficial — referência completa do formato.
- Docker Compose — variáveis de ambiente — as várias formas de passar variável, incluindo
:?.