Docker multi-stage build: imagens menores
Uma imagem Docker de desenvolvimento com todas as dependências de build pode ter 1 GB+. Com multi-stage build, a imagem final de produção tem apenas o que precisa para executar — geralmente 50-150 MB. Menos bytes = pull mais rápido, boot mais rápido, menos surface de ataque e menos custo de armazenamento no registry.
Multi-stage build para Node.js
Separar build de produção em estágios:
dockerfile
# Dockerfile — Node.js com TypeScript
# ── Estágio 1: build ──────────────────────────────────────
FROM node:22-alpine AS builder
WORKDIR /app
# Instalar dependências (separado do COPY . para aproveitar cache):
COPY package*.json ./
RUN npm ci
# Copiar código e compilar TypeScript:
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# ── Estágio 2: produção ───────────────────────────────────
FROM node:22-alpine AS production
WORKDIR /app
# Apenas dependências de produção:
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Copiar apenas o build compilado do estágio anterior:
COPY --from=builder /app/dist ./dist
# Usuário não-root:
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 3000
CMD ["node", "dist/index.js"]
# Comparar tamanho:
# docker build -t minha-api . && docker images minha-api
# Sem multi-stage: ~600 MB
# Com multi-stage: ~120 MBMulti-stage para Python com Poetry
Imagem Python mínima para APIs FastAPI/Flask:
dockerfile
# Dockerfile — Python com Poetry e FastAPI
# ── Estágio 1: builder ────────────────────────────────────
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install poetry
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
COPY pyproject.toml poetry.lock ./
# Instalar apenas dependências de produção:
RUN poetry install --only=main --no-root
COPY . .
# ── Estágio 2: produção ───────────────────────────────────
FROM python:3.12-slim AS production
WORKDIR /app
# Copiar apenas o virtualenv com dependências instaladas:
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src ./src
# Ativar venv:
ENV PATH="/app/.venv/bin:$PATH"
# Usuário não-root:
RUN useradd --create-home app
USER app
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
# Alternativa com imagem distroless (ainda menor):
FROM gcr.io/distroless/python3-debian12 AS production
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src ./src
ENV PATH="/app/.venv/bin:$PATH"
CMD ["/app/.venv/bin/uvicorn", "src.main:app", "--host", "0.0.0.0"]Cache de layers do Docker para builds rápidos
Organizar o Dockerfile para aproveitar o cache ao máximo:
dockerfile
# Regra: o que muda menos frequentemente vai primeiro
# ❌ Antipadrão — COPY . . antes de instalar deps invalida cache sempre:
COPY . .
RUN npm ci # reexecutado a cada mudança de código!
# ✓ Correto — instalar deps antes de copiar código:
COPY package*.json ./
RUN npm ci # cacheado enquanto package.json não mudar
COPY . . # só este layer é invalidado ao mudar código
RUN npm run build
# BuildKit e cache de mount (Docker 23.0+):
# Cache de npm entre builds (não precisa re-baixar packages):
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
# Cache de pip entre builds:
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# Habilitar BuildKit:
# DOCKER_BUILDKIT=1 docker build .
# ou no /etc/docker/daemon.json:
# { "features": { "buildkit": true } }
# Build com cache exportado para CI/CD:
docker buildx build \
--cache-from type=registry,ref=ghcr.io/usuario/api:cache \
--cache-to type=registry,ref=ghcr.io/usuario/api:cache,mode=max \
-t ghcr.io/usuario/api:latest --push .Imagens base: alpine vs slim vs distroless
Escolher a imagem base certa para cada caso:
bash
# Comparação de tamanhos base:
# ubuntu:22.04 → 77 MB (completo, muitas ferramentas)
# debian:bookworm-slim → 74 MB (slim = sem docs e extras)
# node:22 → 1.1 GB (node sobre ubuntu)
# node:22-slim → 230 MB (node sobre debian-slim)
# node:22-alpine → 58 MB (node sobre Alpine Linux)
# gcr.io/distroless/nodejs22 → 102 MB (apenas Node.js, sem shell)
# Alpine Linux — mais compacto, usa musl libc:
# Vantagem: imagens muito menores
# Desvantagem: alguns pacotes nativos precisam ser recompilados
# Use quando: Node.js puro, Python sem extensões C complexas
# Slim — Debian sem extras:
# Vantagem: mais compatível que Alpine, ainda compacto
# Use quando: libs nativas (sharp, bcrypt, tensorflow)
# Distroless — sem shell, sem package manager:
# Vantagem: mínima surface de ataque (sem bash = sem exploração de shell)
# Desvantagem: difícil de debugar (exec -it não tem shell)
# Use quando: segurança é prioridade máxima
# Adicionar shell temporário para debug em distroless:
docker run --rm -it --entrypoint sh minha-api:debug # usar tag com shell para dev
# docker run -it minha-api:prod (sem shell — correto para prod)Verificar e reduzir tamanho de imagens
Ferramentas para analisar o que engorda as imagens:
bash
# Ver tamanho de imagens:
docker images --format "table {{.Repository}} {{.Tag}} {{.Size}}"
# Dive — inspecionar cada layer da imagem:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive minha-api:latest
# Ver o que está em cada layer:
docker history minha-api:latest
# Limpar imagens e layers não utilizados:
docker image prune -a # remove imagens sem tag e intermediárias
docker system prune -a --volumes # limpeza total (cuidado!)
# Verificar quais arquivos foram copiados:
docker run --rm minha-api:latest find / -name "*.ts" 2>/dev/null
# Se achar .ts: você copiou fonte TypeScript para produção
# .dockerignore — excluir arquivos desnecessários:
node_modules/
src/
*.ts
!dist/
.env*
.git/
tests/
*.test.js
coverage/$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para Docker em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.