Dicas para escrever Dockerfiles mais eficientes
1. Otimização da camada de base
A escolha da imagem base é o primeiro e mais impactante passo para escrever Dockerfiles eficientes. Imagens oficiais como Alpine e Slim reduzem drasticamente o tamanho final e a superfície de ataque.
# Ruim: imagem pesada e tag genérica
FROM node:latest
# Bom: imagem leve com tag específica
FROM node:20-alpine
Por que Alpine? A imagem base Alpine Linux tem cerca de 5 MB, enquanto versões completas do Ubuntu ou Debian ultrapassam 100 MB. Para aplicações Node.js, Python ou Go, essa diferença é significativa.
# Exemplo prático: Python com imagem slim
FROM python:3.12-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
Sempre especifique tags exatas como 20-alpine ou 3.12-slim em vez de latest. A tag latest muda com o tempo e quebra builds reproduzíveis.
2. Ordenação estratégica de instruções
O Docker constrói imagens em camadas e cada instrução RUN, COPY ou ADD cria uma nova camada. O cache de camadas é invalidado quando uma instrução muda. Portanto, ordene as instruções das menos voláteis para as mais voláteis.
# Eficiente: instala dependências antes de copiar código
FROM node:20-alpine
WORKDIR /app
# Primeiro: dependências (mudam raramente)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Depois: código-fonte (muda frequentemente)
COPY . .
CMD ["node", "server.js"]
Nesse exemplo, se apenas o código-fonte mudar, o Docker reutiliza as camadas de instalação de dependências do cache, acelerando o rebuild em segundos.
3. Redução do tamanho da imagem
Combine comandos RUN em um único layer e limpe artefatos temporários imediatamente.
# Ineficiente: múltiplos layers e sem limpeza
RUN apt-get update
RUN apt-get install -y curl git
RUN apt-get clean
# Eficiente: um único layer com limpeza
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
O .dockerignore é essencial para evitar que arquivos desnecessários entrem no contexto de build:
# .dockerignore
node_modules
.git
*.md
.gitignore
.env
dist
# Exemplo de Dockerfile com .dockerignore eficiente
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 alpine:3.19
COPY --from=builder /app/server /server
CMD ["/server"]
4. Gerenciamento de dependências
Instale apenas pacotes essenciais para produção e separe dependências de build das de runtime.
# Python: separação de dependências
FROM python:3.12-slim
WORKDIR /app
# Apenas dependências de produção
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Para projetos com lockfiles, copie-os antes do código para aproveitar o cache:
# Node.js com lockfile
FROM node:20-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
COPY . .
CMD ["yarn", "start"]
5. Uso de multi-stage builds
Multi-stage builds são a técnica mais poderosa para reduzir o tamanho da imagem final. Crie um estágio de build com todas as ferramentas necessárias e copie apenas os artefatos para o estágio final.
# Exemplo completo com Go
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/app .
# Estágio final: apenas o binário
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /bin/app /app
USER nobody
CMD ["/app"]
# Node.js com multi-stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
USER node
CMD ["node", "dist/server.js"]
6. Boas práticas de segurança
Execute aplicações com usuário não-root e prefira COPY em vez de ADD.
# Inseguro: execução como root
FROM node:20-alpine
COPY . /app
CMD ["node", "app.js"]
# Seguro: usuário não-root
FROM node:20-alpine
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "app.js"]
ADD tem comportamentos imprevisíveis com arquivos compactados e URLs. Use COPY para transferir arquivos locais e RUN curl para downloads.
# Evite ADD para downloads
# ADD https://example.com/file.tar.gz /tmp/
# Prefira COPY + RUN
COPY scripts/download.sh .
RUN ./download.sh && rm download.sh
Escaneie vulnerabilidades regularmente:
# Exemplo de comando para escanear (fora do Dockerfile)
docker scout quickview minha-imagem:tag
7. Dicas avançadas de performance
Use --mount=type=cache para acelerar instalações repetitivas em builds.
# Cache de pacotes npm
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
COPY . .
CMD ["node", "server.js"]
# Cache de pacotes apt
FROM ubuntu:22.04
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
Adicione HEALTHCHECK para monitoramento eficiente:
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
USER node
CMD ["node", "server.js"]
Conclusão
Escrever Dockerfiles eficientes reduz o tempo de build, o tamanho da imagem e melhora a segurança. Comece com imagens base leves, ordene instruções estrategicamente, use multi-stage builds e aplique boas práticas de segurança. O cache de camadas e o .dockerignore são ferramentas simples que geram ganhos imediatos. Com essas técnicas, seus containers serão mais rápidos, menores e mais seguros.
Referências
- Documentação oficial do Docker: Best practices for writing Dockerfiles — Guia completo com as melhores práticas recomendadas pela equipe do Docker.
- Docker: Multi-stage builds — Tutorial oficial sobre como usar multi-stage builds para reduzir o tamanho das imagens.
- Docker: .dockerignore file — Referência completa sobre como usar o arquivo .dockerignore para excluir arquivos desnecessários do contexto de build.
- Snyk: 10 Docker Security Best Practices — Artigo técnico sobre segurança em imagens Docker, incluindo usuário não-root e escaneamento de vulnerabilidades.
- Node.js Docker: Official Best Practices — Guia oficial da Node.js Foundation para criar Dockerfiles eficientes para aplicações Node.js.