Docker Compose: subir API, rede e volume com um comando
Crie um compose.yaml para uma API Node, use variável obrigatória, healthcheck, rede e volume e diagnostique a configuração antes de iniciar.
Docker Compose lê um arquivo compose.yaml e cria os serviços, redes e
volumes de uma aplicação como um único projeto. Nesta lição você vai subir uma
API Node, esperar seu healthcheck, chamar a API por nome em outro contêiner e
preservar um volume depois do down.
Esta é a sexta etapa da trilha Docker. Ela junta o que já foi provado em redes Docker e volumes Docker; Compose automatiza essas peças, mas não muda o mecanismo delas.
A ficha de abertura organiza todas as equipes
Imagine a abertura de uma feira de bairro. A ficha do evento diz quais barracas
existem, qual depósito usam, em que circuito elétrico entram e qual inspeção uma
barraca precisa passar antes de outra começar o atendimento. A ficha é o
compose.yaml; cada barraca é um serviço; o circuito é a rede; o depósito é o
volume; a inspeção é o healthcheck.
Voltando aos termos técnicos: um projeto Compose agrupa recursos com um nome.
Cada service vira um ou mais contêineres. Compose cria uma rede padrão e
registra os nomes dos serviços no DNS. Volumes declarados no topo recebem nomes
associados ao projeto.
A analogia não significa que Compose executa a lógica da aplicação. Ele cria e coordena recursos. Quem responde HTTP é o Node; quem decide se uma rota está correta é seu código.
A aplicação deixa duas provas visíveis
Crie server.js. Além de responder HTTP, ele grava no volume a mensagem
recebida por variável de ambiente:
import { createServer } from 'node:http';
import { writeFileSync } from 'node:fs';
const port = 3000;
const mensagem = process.env.MENSAGEM;
writeFileSync('/dados/ultima-mensagem.txt', `${mensagem}\n`);
const server = createServer((request, response) => {
const body = request.url === '/health'
? { status: 'ok' }
: { servico: 'cantina', mensagem, node: process.version };
response.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
});
response.end(JSON.stringify(body));
});
server.listen(port, '0.0.0.0', () => {
console.log(`API pronta na porta ${port}; mensagem gravada no volume`);
});O endpoint /health responde sem depender do arquivo. Em projeto real, o
healthcheck deve verificar aquilo que define prontidão sem provocar alteração de
estado a cada consulta.
O package.json mantém o comando explícito:
{
"name": "cantina-compose",
"private": true,
"type": "module",
"scripts": {
"start": "node server.js"
}
}A imagem prepara o ponto de montagem
O Dockerfile cria /dados, entrega a pasta ao usuário sem privilégio e inicia a
API:
FROM node:24-alpine
WORKDIR /app
COPY --chown=node:node package.json server.js ./
RUN mkdir /dados && chown node:node /dados
USER node
EXPOSE 3000
CMD ["npm", "start"]Quando um volume vazio é montado nesse caminho, a aplicação node consegue
gravar sem voltar a rodar como root. A lógica de cada instrução foi construída
na lição de Dockerfile para Node.
Exclua arquivos que não pertencem ao contexto:
node_modules
.git
.envO .env fica fora da imagem. Isso evita cópia acidental; não transforma o
arquivo local em cofre.
compose.yaml descreve o conjunto
Salve este arquivo ao lado dos anteriores:
name: cantina-devclub
services:
api:
build: .
environment:
MENSAGEM: ${MENSAGEM:?defina MENSAGEM}
ports:
- "127.0.0.1:3077:3000"
volumes:
- historico:/dados
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))
interval: 2s
timeout: 1s
retries: 10
verificador:
image: node:24-alpine
depends_on:
api:
condition: service_healthy
command:
- node
- -e
- fetch('http://api:3000').then(r=>r.text()).then(console.log)
volumes:
historico:api é construída localmente. verificador usa uma imagem pronta e chama
http://api:3000: o nome do serviço vira hostname na rede padrão. Ele não usa a
porta 3077, porque essa é a entrada do host; entre serviços vale a porta 3000 do
contêiner.
depends_on com condition: service_healthy faz o verificador esperar o
healthcheck da API. Essa espera organiza a inicialização, mas uma aplicação de
produção ainda precisa reconectar se a dependência cair depois.
Não existe campo version no topo. Compose 5 usa a Compose Specification atual;
os antigos formatos 2 e 3 foram incorporados.
Erro real: variável obrigatória ausente
Antes de iniciar, peça ao Compose para resolver e validar a configuração:
docker compose configO operador ${MENSAGEM:?defina MENSAGEM} é interpolação obrigatória. Em
português simples, Compose se recusa a montar o projeto se a variável estiver
ausente ou vazia. O erro acontece antes de criar imagem, rede ou contêiner.
Use um valor público de laboratório e liste os serviços resolvidos:
MENSAGEM='pedidos abertos' docker compose config --servicesPara configurações sensíveis, evite colar a saída completa de docker compose config em tickets: ela pode conter valores interpolados. A ideia de separar
configuração está detalhada em
variáveis de ambiente no Node.
up constrói, cria e inicia na ordem necessária
Forneça a mesma variável ao comando que sobe o projeto:
MENSAGEM='pedidos abertos' docker compose up --build -dAs linhas internas do cache de build foram omitidas acima; as seis linhas são
da execução real e mostram o ciclo do projeto. --build atualiza a imagem da
API. -d devolve o terminal depois de iniciar os serviços.
Espere um instante e leia os estados sem misturar outros projetos:
MENSAGEM='pedidos abertos' docker compose ps -a --format '{{.Service}} | {{.State}} | {{.Health}}'verificador encerrar não é falha. Seu trabalho era fazer uma requisição e sair
com sucesso. api continua rodando e saudável.
Três observações comprovam rede, porta e volume
Do host, chame a porta publicada:
curl -sS http://127.0.0.1:3077O log do verificador mostra que outro contêiner recebeu o mesmo JSON pelo nome
api, sem usar a porta do host:
MENSAGEM='pedidos abertos' docker compose logs --no-color verificadorPor fim, leia o arquivo montado sem descobrir nome de contêiner manualmente:
MENSAGEM='pedidos abertos' docker compose exec api node -e "console.log(require('node:fs').readFileSync('/dados/ultima-mensagem.txt','utf8').trim())"docker compose exec api resolve qual contêiner implementa o serviço. O texto
veio de /dados, caminho sustentado pelo volume historico.
logs, ps e config formam o primeiro diagnóstico
Quando up não entrega a aplicação, siga uma ordem que preserve evidência:
docker compose configencontra interpolação e estrutura inválida antes do runtime;docker compose ps -amostra processo encerrado e estado do healthcheck;docker compose logs apimostra o que o processo principal explicou;docker compose exec api ...serve para uma inspeção específica quando os logs não bastam.
Não comece com down -v, rebuild sem cache e limpeza global. Apagar estado pode
esconder a causa e destruir o único dado que permitiria reproduzir o problema.
down remove execução, não o dado nomeado
Pare o projeto usando o mesmo valor de interpolação:
MENSAGEM='pedidos abertos' docker compose downCompose remove os contêineres e a rede padrão. O volume continua:
docker volume ls --filter label=com.docker.compose.project=cantina-devclub --format '{{.Name}}'docker compose down -v incluiria volumes declarados e apagaria esses dados.
Use -v somente quando o ambiente for descartável e o alvo estiver conferido.
Missão: mudar configuração sem mudar a imagem
Suba o mesmo projeto com MENSAGEM='turno da tarde', sem editar server.js nem
o Dockerfile. O critério de sucesso tem quatro partes: api fica healthy; o
host recebe turno da tarde; o log do verificador recebe o mesmo valor pelo
DNS interno; o arquivo no volume também contém a nova mensagem.
Depois execute down sem -v e confirme que o volume ainda aparece. Esse teste
separa três responsabilidades: a imagem traz o programa, a variável configura a
execução e o volume guarda dado fora do contêiner.
Você fechou o circuito da trilha. O próximo passo profissional é preparar o deploy de uma API Node, adicionando registro, segredos, observação e atualização. O guia de Docker permanece como mapa para revisar qualquer peça.
Perguntas frequentes
O arquivo ainda precisa declarar version no topo?
docker compose up recria tudo em toda execução?
depends_on garante que o banco está pronto?
Posso guardar senha no arquivo .env do Compose?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Docker Engine 29.5.3, Docker Compose 5.1.4 e Node 24.19.0 (node:24-alpine) no macOS ARM64, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Docker Docs — Docker Compose — docs.docker.com
- Docker Docs — Compose file reference — docs.docker.com
- Docker Docs — Networking in Compose — docs.docker.com
- Docker Docs — Control startup order — docs.docker.com
- Docker Docs — Variable interpolation — docs.docker.com


