9

Docker para Devs: do Dockerfile ao docker-compose em Produção

O erro mais caro de Docker custa zero em disco e derruba tudo em runtime

Um Dockerfile de 3 linhas funciona no laptop. Em produção, a imagem pesa 1.2 GB, roda como root, não tem healthcheck e o container reinicia em loop porque o processo principal morreu sem que o orquestrador soubesse. O problema nunca é "Docker não funciona". O problema é o que você não configurou.

Este post cobre o caminho do Dockerfile mínimo até um docker-compose.yml de produção com healthchecks, limites de memória, secrets e multi-stage build. O foco é Node.js/TypeScript, mas a estrutura se aplica a qualquer runtime.

Multi-stage build: por que sua imagem não deveria passar de 200 MB

A ideia é simples: use um estágio para compilar, outro para rodar. O estágio de build carrega devDependencies, TypeScript compiler, ferramentas de lint. O estágio final copia só o artefato compilado e as dependências de produção.

# === Estágio 1: build ===
FROM node:20-alpine AS builder

WORKDIR /app

# Copia package*.json primeiro para aproveitar cache de camadas.
# Se o código mudar mas as dependências não, essa camada não rebuilda.
COPY package.json package-lock.json ./
RUN npm ci

COPY tsconfig.json ./
COPY src ./src
RUN npm run build

# === Estágio 2: produção ===
FROM node:20-alpine AS production

# Roda como usuário não-root. O user "node" já existe na imagem alpine do Node.
USER node

WORKDIR /app

COPY --from=builder --chown=node:node /app/package.json /app/package-lock.json ./
# --omit=dev instala só dependências de produção, eliminando typescript, eslint, etc.
RUN npm ci --omit=dev

COPY --from=builder --chown=node:node /app/dist ./dist

EXPOSE 3000

# node direto, sem npm start. npm start spawna um processo filho,
# e sinais como SIGTERM não propagam corretamente para o processo Node.
CMD ["node", "dist/server.js"]

A diferença prática: uma imagem single-stage com node:20 pesa ~900 MB. Com multi-stage e node:20-alpine, fica entre 120 e 180 MB dependendo das dependências nativas.

AbordagemImagem baseTamanho finaldevDependencies incluídasRoda como root
Single-stage node:20~350 MB800-1200 MBSimSim (padrão)
Single-stage node:20-alpine~50 MB300-500 MBSimSim (padrão)
Multi-stage node:20-alpine~50 MB120-180 MBNãoNão (USER node)

.dockerignore: a linha que evita vazamento de secrets

Sem .dockerignore, o COPY . . manda tudo para o daemon: node_modules local, .env com credenciais, .git inteiro. Crie o arquivo na raiz do projeto:

node_modules
.git
.gitignore
.env
.env.*
dist
coverage
*.md
docker-compose*.yml
Dockerfile
.dockerignore

Cada linha é um padrão glob. O ponto crítico é .env: se ele entra na imagem, qualquer pessoa com acesso ao registry lê suas credenciais com docker history ou extraindo camadas.

Healthcheck no Dockerfile: o container sabe que está vivo

Sem healthcheck, o Docker (e qualquer orquestrador) só sabe se o processo está rodando. Um processo que consome 100% de CPU em deadlock continua "running". O healthcheck resolve isso:

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

O endpoint /health precisa existir na aplicação. Não precisa ser sofisticado:

// src/routes/health.ts
import { Router, Request, Response } from "express";

const router = Router();

router.get("/health", (_req: Request, res: Response) => {
  // Retorna 200 se o processo responde HTTP.
  // Para checks mais profundos (DB, Redis), adicione verificações aqui.
  res.status(200).json({ status: "ok" });
});

export { router as healthRouter };

O --start-period=10s dá tempo para a aplicação inicializar antes de começar a verificar. Se sua aplicação demora mais (migrations, warm-up de cache), aumente esse valor.

docker-compose.yml para produção real

O docker-compose.yml abaixo orquestr


Leia o artigo completo em https://www.vivodecodigo.com.br/infra/docker-dockerfile-docker-compose-producao-guia-pratico

Carregando publicação patrocinada...
1

Meus 2 cents,

Parabens pelo post !

Muitas vezes quem produz o docker para producao esquece de fazer estas otimizacoes - que fazem diferenca no resultado final.

Um detalhe, a frase do final do artigo ficou cortado, acredito que por conta da limitacao do espaco (provavelmente processo automatizado de publicacao). Ou nao, afinal com o corte induziu a curiosidade para ver o complemento no site de origem.

Saude e Sucesso !


Este post foi favoritado via extensão TABNEWS FAVORITOS

Tem curiosidade sobre IA ? Da uma olhada no meu LIVRO: IA PARA ENGENHEIROS

-1