Dicas para reduzir o tamanho de imagens Docker

Imagens Docker grandes são um problema comum em ambientes de produção. Elas consomem mais espaço em disco, aumentam o tempo de deploy e tornam o pull de imagens mais lento em clusters Kubernetes. Felizmente, existem técnicas comprovadas para reduzir significativamente o tamanho das suas imagens sem comprometer a funcionalidade.

1. Escolha a imagem base correta

A escolha da imagem base é o fator que mais impacta o tamanho final. Uma imagem Ubuntu completa pode ter mais de 200 MB, enquanto alternativas mais enxutas oferecem reduções drásticas.

# Ruim: imagem completa do Ubuntu
FROM ubuntu:22.04
# Tamanho: ~200 MB

# Bom: versão slim do Python
FROM python:3.11-slim
# Tamanho: ~120 MB

# Melhor: Alpine Linux
FROM python:3.11-alpine
# Tamanho: ~45 MB

# Excelente: Distroless (apenas runtime)
FROM gcr.io/distroless/python3
# Tamanho: ~30 MB

Sempre evite usar a tag latest. Prefira tags específicas como python:3.11-slim-bookworm para garantir reprodutibilidade e evitar surpresas com atualizações.

2. Otimização de camadas com Dockerfile multi-stage

O multi-stage build é uma das técnicas mais poderosas para reduzir tamanho. Você cria um ambiente de build com todas as ferramentas necessárias e depois copia apenas os artefatos finais para uma imagem mínima.

# Estágio 1: Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server

# Estágio 2: Runtime
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]

A imagem final conterá apenas o binário compilado e as bibliotecas essenciais, reduzindo de ~800 MB (Go completo) para ~15 MB.

3. Minimize o número de camadas e comandos RUN

Cada instrução RUN, COPY ou ADD cria uma nova camada. Combine comandos relacionados em um único RUN para reduzir o número de camadas e o tamanho final.

# Ruim: múltiplos RUNs criam várias camadas
RUN apt-get update
RUN apt-get install -y curl git
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

# Bom: comandos combinados em um único RUN
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

A segunda abordagem não apenas reduz camadas, mas também garante que arquivos temporários sejam removidos na mesma camada em que foram criados, evitando que ocupem espaço desnecessário.

4. Gerencie dependências de forma inteligente

Instale apenas o necessário para produção e remova arquivos que não são úteis em runtime.

FROM node:20-alpine AS build

WORKDIR /app
COPY package*.json ./

# Instalar apenas dependências de produção
RUN npm ci --only=production && \
    npm cache clean --force

COPY . .

# Remover arquivos de desenvolvimento
RUN rm -rf tests/ docs/ *.md .gitignore

Para Python, use o pip com flags apropriadas:

FROM python:3.11-alpine

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && \
    find /usr/local -type f -name "*.pyc" -delete && \
    find /usr/local -type d -name "__pycache__" -delete

5. Utilize .dockerignore e compressão de camadas

O arquivo .dockerignore evita que arquivos desnecessários sejam enviados para o daemon Docker, reduzindo o contexto de build e o tamanho das camadas.

# .dockerignore
node_modules
.git
.env
*.md
tests/
docs/
Dockerfile
.gitignore
.vscode/
__pycache__/
*.pyc
dist/
*.log

Lembre-se que o Docker reutiliza camadas em cache. Se você copiar node_modules inteiro e depois removê-lo em um comando RUN, o espaço ainda será ocupado na camada anterior. Sempre evite copiar diretórios que serão removidos posteriormente.

6. Compactação e pós-processamento da imagem final

Ferramentas especializadas podem analisar e reduzir ainda mais o tamanho das imagens.

# Usando docker-slim para otimizar imagem existente
docker-slim build --target myapp:latest --tag myapp:slim

# Analisando camadas com dive
dive myapp:latest

O dive mostra o tamanho de cada camada e identifica arquivos desnecessários. O docker-slim pode reduzir imagens em até 90% removendo bibliotecas não utilizadas.

Para binários totalmente estáticos (Go, Rust), considere usar scratch como base:

FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]

7. Monitoramento contínuo e boas práticas de CI/CD

Integre verificações de tamanho no seu pipeline para evitar que imagens cresçam com o tempo.

# Exemplo de workflow GitHub Actions
name: Docker Image Check

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Build and analyze
        run: |
          docker build -t myapp:latest .
          docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
            wagoodman/dive:latest myapp:latest

      - name: Check image size
        run: |
          SIZE=$(docker images myapp:latest --format "{{.Size}}" | \
            sed 's/MB//' | sed 's/GB//')
          if (( $(echo "$SIZE > 200" | bc -l) )); then
            echo "Image too large: $SIZE MB"
            exit 1
          fi

Defina limites claros: imagens Python/Node abaixo de 200 MB, Go/Rust abaixo de 50 MB. Use ferramentas como Trivy ou Snyk para identificar dependências desnecessárias e vulnerabilidades.

Resumo das principais técnicas

Técnica Redução típica Esforço
Imagem base Alpine 70-80% Baixo
Multi-stage build 50-90% Médio
Combinar RUNs 10-30% Baixo
.dockerignore 20-50% Baixo
docker-slim 50-90% Alto

Comece pelas técnicas de menor esforço (imagem base, .dockerignore) e avance para multi-stage e docker-slim conforme necessário. Cada megabyte economizado em uma imagem Docker se multiplica pelo número de deploys e ambientes, resultando em economia significativa de tempo e recursos.

Referências