PureTools

Dockerfile: Boas Práticas para Builds Menores e Mais Rápidos

PureTools Team· 9 min de leitura
Dockerfile: Boas Práticas para Builds Menores e Mais Rápidos

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écnicaImpactoPor Quê
node:20-alpine~900MB menorAlpine Linux tem ~5MB vs ~120MB do Debian
Multi-stage buildSem ferramentas de build na imagem finalApenas artefatos de produção são copiados para o estágio final
npm ci vs npm installBuilds determinísticosUsa lockfile exatamente, mais rápido, sem surpresas
Copiar package.json primeiroMelhor cache de camadasDependências só reinstalam quando package.json muda
USER appuserSegurançaContainer não roda como root
--only=productionnode_modules menorSem 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_Store

Sem 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-20MB

Checklist 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-alpine não node:latest
  • Escaneie vulnerabilidades — docker scout quickview ou trivy image myapp
  • Use .dockerignore para excluir .env, .git e arquivos sensíveis

Referência Docker: Cheatsheet Docker — todos os comandos que você precisa, categorizados e pesquisáveis.