Dockerfile: De 1.2GB para 80MB
Um Dockerfile ingênuo para um app Node.js produz uma imagem de 1.2GB. Um bem escrito produz 80MB. A diferença é entender como layers Docker funcionam e aplicar alguns padrões consistentemente.
O Dockerfile Ingênuo
# NÃO faça isso
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]Problemas: imagem Node.js completa (1GB+), copia node_modules se existem localmente, instala devDependencies, sem cache de camadas, roda como root.
O Dockerfile de Produção
# Stage 1: Instalar dependências
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Imagem de produção
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Não rode como root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]Otimizações Chave Explicadas
| Técnica | Impacto | Por Quê |
|---|---|---|
node:20-alpine | ~900MB menor | Alpine Linux tem ~5MB vs ~120MB do Debian |
| Multi-stage build | Sem ferramentas de build na imagem final | Apenas artefatos de produção são copiados para o estágio final |
npm ci vs npm install | Builds determinísticos | Usa lockfile exatamente, mais rápido, sem surpresas |
| Copiar package.json primeiro | Melhor cache de camadas | Dependências só reinstalam quando package.json muda |
USER appuser | Segurança | Container não roda como root |
--only=production | node_modules menor | Sem devDependencies em produção |
Cache de Camadas: Ordem Importa
Docker cacheia cada camada. Quando uma camada muda, todas as subsequentes são reconstruídas. Então coloque coisas que mudam raramente no topo:
# Raramente muda → cache hit
FROM node:20-alpine
WORKDIR /app
# Muda quando dependências mudam
COPY package.json package-lock.json ./
RUN npm ci
# Muda a cada mudança de código → invalida apenas esta camada
COPY . ..dockerignore
Tão importante quanto .gitignore:
node_modules
.git
.env
*.md
Dockerfile
docker-compose.yml
.next
coverage
tests
.vscode
.DS_StoreSem isso, COPY . . envia todo seu node_modules (e histórico git) para o daemon Docker, tornando builds lentos antes mesmo de começar.
Dockerfile Python
# Build multi-stage Python
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]Dockerfile Go (Imagem Final Minúscula)
# Go compila para binário estático — imagem final pode ser scratch
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# Imagem final: ~10-20MBChecklist de Segurança
- Nunca rode como root — sempre crie e use um usuário não-root
- Não armazene segredos na imagem — use variáveis de ambiente ou secret mounts
- Fixe versões de imagem —
node:20.11.1-alpinenãonode:latest - Escaneie vulnerabilidades —
docker scout quickviewoutrivy image myapp - Use
.dockerignorepara excluir.env,.gite arquivos sensíveis
Referência Docker: Cheatsheet Docker — todos os comandos que você precisa, categorizados e pesquisáveis.