Docker: Otimização de Imagens e Melhores Práticas

Última atualização: 2026-08-26

Otimizar imagens não é apenas reduzir o tamanho — imagens menores significam implantações mais rápidas, custos de armazenamento mais baixos e uma superfície de ataque menor.

1. O Que Você Vai Aprender



2. Uma História Real de uma Pessoa Engenheira de CI

(1) Ponto de Dor: O tempo de build do CI disparou de 8 para 25 minutos

O tempo de build do CI de Bob disparou subitamente de 8 para 25 minutos. Após investigação, ele descobriu que cada build estava reinstalando todas as dependências — porque COPY . . estava posicionado antes de RUN pip install, fazendo com que o cache de instalação de dependências expirasse a cada mudança de código. A espera de 20 minutos reduziu significativamente a eficiência do desenvolvimento.

(2) Soluções para Otimização de Cache

Ao reorganizar a ordem das instruções do Dockerfile e mover o pouco alterado COPY package.json para o início, conseguimos usar o cache de camadas para reduzir o tempo de build de volta para 3 minutos.

DOCKERFILE
# Antes: mudança de código invalida o cache do pip
COPY . .
RUN pip install -r requirements.txt

# Depois: taxa de mudança de dependência << taxa de mudança de código
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

(3) Benefícios: Tempo de build reduzido de 25 para 3 minutos

Simplesmente reorganizando a ordem de duas linhas de código, o tempo de build caiu de 25 para 3 minutos (quando o cache é atingido), resultando em um aumento de 8 vezes na eficiência do pipeline de CI.



3. Estratégias-Chave para Otimização de Imagens

(1) Quadro Comparativo Antes e Depois

100%
graph TB
    subgraph Before["Antes da Otimização"]
        B1["FROM ubuntu:22.04<br/>77 MB"] --> B2["RUN apt-get install<br/>300 MB"]
        B2 --> B3["COPY . .<br/>(com) node_modules"]
        B3 --> B4["RUN npm install<br/>200 MB"]
        B4 --> B5["Final: 1,2 GB"]
    end
    subgraph After["Após a Otimização"]
        A1["FROM node:20-alpine<br/>135 MB"] --> A2["COPY package.json"]
        A2 --> A3["RUN npm ci<br/>50 MB"]
        A3 --> A4["COPY . .<br/>(sem) node_modules"]
        A4 --> A5["Final: 180 MB"]
    end

(2) Visão Geral das Estratégias de Otimização

Estratégia Efetividade Dificuldade de Implementação
Mudar para imagem base (alpine/slim) Tamanho ↓50–80%
Build multi-estágio Volume ↓60–90% ⭐⭐
RUN Combinar + Limpar Cache Tamanho ↓20–40%
Ajustar Ordem do COPY Velocidade de Build ↑3–8x
.dockerignore Contexto ↓30–90%
Verificação com hadolint Problemas potenciais encontrados ⭐⭐


4. Combinando Comandos RUN

(1) Princípio da Combinação

Cada comando RUN cria uma nova camada. Combine operações relacionadas em um único comando RUN e limpe arquivos temporários na mesma camada.

▶ Exemplo: Combinando Comandos (Dificuldade: ⭐⭐)

DOCKERFILE
# Ruim: 3 camadas separadas, cache do apt permanece na imagem
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y nginx

# Bom: 1 camada, cache limpo na mesma camada
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        nginx && \
    rm -rf /var/lib/apt/lists/*

(2) O efeito de --no-install-recommends

Opção Conteúdo da Instalação Economia Típica de Espaço
Padrão Pacotes principais + Pacotes recomendados + Pacotes sugeridos Linha de base
--no-install-recommends Pacote principal + dependências apenas ↓30–50%


5. Otimizando a Taxa de Acerto do Cache

(1) Mecanismo de Cache de Camadas

Quando o Docker constrói uma imagem, se as instruções e o contexto de uma camada permanecem inalterados, o cache é reutilizado. Uma vez que o cache de uma camada expira, todas as camadas subsequentes devem ser reconstruídas.

100%
graph TB
    L1["FROM python:3.12<br/>✅ Cache Atingido"] --> L2["WORKDIR /app<br/>✅ Cache Atingido"]
    L2 --> L3["COPY requirements.txt<br/>✅ Cache Atingido (sem alteração)"]
    L3 --> L4["RUN pip install<br/>✅ Cache Atingido"]
    L4 --> L5["COPY . .<br/>❌ Cache Perdido (código alterado)"]
    L5 --> L6["CMD python app.py<br/>❌ Reconstruir"]

▶ Exemplo: Comparando builds com e sem --no-cache (Dificuldade: ⭐⭐)

BASH
# Build normal: usa cache quando possível
docker build -t myapp:1.0 .

# Forçar reconstrução sem qualquer cache
docker build --no-cache -t myapp:1.0 .

# Usar uma imagem anterior como fonte de cache (otimização de CI)
docker build --cache-from=myapp:latest -t myapp:1.0 .

(2) Otimizar a ordem das instruções do Dockerfile

DOCKERFILE
# ============================================
# Dockerfile otimizado: alta taxa de acerto de cache
# ============================================

FROM node:20-alpine

WORKDIR /app

# 1. Menos alterado: metadados do pacote
COPY package*.json ./

# 2. Instalação de dependências (em cache a menos que package.json mude)
RUN npm ci --only=production

# 3. Mais alterado: código da aplicação
COPY . .

EXPOSE 3000
CMD ["node", "server.js"]
Mudanças no Cenário Versão Otimizada Versão Não Otimizada
Apenas código alterado ✅ npm ci usa cache (5 segundos) ❌ npm ci reconstrói (3 minutos)
Atualizar versão de dependência ✅ Reconstrói com npm ci (necessário) ❌ Reconstrói com npm ci (igual acima)
Modificar Dockerfile ✅ Reconstrói apenas as camadas afetadas ❌ Reconstrói tudo


6. Ferramentas de Análise de Imagens

▶ Exemplo: Analisando Camadas de Imagem com dive (Dificuldade: ⭐⭐)

dive é uma ferramenta interativa de análise de camadas de imagem que mostra quais arquivos foram adicionados, modificados ou excluídos em cada camada.

BASH
# Instalar dive (Linux)
wget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.deb
sudo dpkg -i dive_0.12.0_linux_amd64.deb

# Analisar uma imagem
dive nginx:latest
💡 Dica: A interface do dive exibe: ① o tamanho e a pontuação de eficiência de cada camada à esquerda; ② a árvore de arquivos adicionada a cada camada à direita. Marcações vermelhas indicam espaço desperdiçado (arquivos que foram sobrescritos ou excluídos por camadas subsequentes).

▶ Exemplo: Verificando um Dockerfile com hadolint (Dificuldade: ⭐⭐)

hadolint é uma ferramenta de lint para Dockerfiles que verifica violações das melhores práticas.

BASH
# Instalar hadolint (Linux)
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint

# Verificar um Dockerfile
hadolint Dockerfile
💻 Saída:

TEXT 📖 Somente leitura
Dockerfile:3 DL3013 warning: Pin versions in pip. Instead of `pip install <package>` use `pip install <package>==<version>`
Dockerfile:5 DL3059 info: Do not use `--no-cache-dir` in pip install. It is redundant when `--cache-from` is used.
Dockerfile:7 DL3008 warning: Pin versions in apt-get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`


7. Comparação Completa do Dockerfile Antes e Depois da Otimização

(1) Exemplos de Otimização de Aplicação Node.js

DOCKERFILE
# ============================================
# ANTES: Dockerfile não otimizado (1,2 GB)
# ============================================
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]
DOCKERFILE
# ============================================
# DEPOIS: Dockerfile otimizado (180 MB)
# ============================================
# 1. Base Alpine em vez de Node completo
FROM node:20-alpine

WORKDIR /app

# 2. Copiar metadados de dependência primeiro (otimização de cache)
COPY package*.json ./

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

# 4. Copiar código fonte (mais alterado)
COPY . .

# 5. Executar como usuário não-root
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup && \
    chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

(2) Comparação dos Resultados da Otimização

Métrica Antes da Otimização Após a Otimização Melhoria
Tamanho da Imagem 1,2 GB 180 MB 6,7x ↓
Tempo de Build (Mudanças de Código) 3 minutos 5 segundos 36x ↓
Tempo de Build (Mudanças de Dependência) 3 minutos 45 segundos 4x ↓
Vulnerabilidades de Segurança 200+ 30 6,7x ↓
Pontuação CIS Benchmark 45 85 +40


8. Políticas de Instalação Específicas por Linguagem

Linguagem Dependências Comando de Instalação Comando de Limpeza
Node.js package*.json npm ci --only=production npm cache clean --force
Python requirements.txt pip install --no-cache-dir (Integrado --no-cache-dir)
Go go.mod go.sum go mod download go clean -modcache
Java pom.xml / build.gradle mvn dependency:resolve Limpar cache .m2
Ruby Gemfile Gemfile.lock bundle install --without dev Limpar cache .bundle


9. Exemplo Completo: Otimização Ponta a Ponta de uma Aplicação Node.js

DOCKERFILE
# ============================================
# Dockerfile de produção Node.js totalmente otimizado
# Recursos: alpine, multi-estágio, cache, não-root
# ============================================

# ---------- Estágio 1: Build ----------
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# ---------- Estágio 2: Produção ----------
FROM node:20-alpine

WORKDIR /app

# Copiar apenas dependências de produção
COPY package*.json ./
RUN npm ci --only=production && \
    npm cache clean --force

# Copiar aplicação compilada do builder
COPY --from=builder /app/dist ./dist

# Segurança: usuário não-root
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup && \
    chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]
BASH
# Construir e verificar
docker build -t node-app:optimized .

# Comparar tamanhos
docker images node-app

# Analisar com dive
dive node-app:optimized

# Verificar com hadolint
hadolint Dockerfile

❓ Perguntas Frequentes

P: Cada RUN aumenta o tamanho da imagem? R: Sim, mesmo que o RUN apenas exclua arquivos — porque uma nova camada registra a "operação de exclusão", enquanto a camada subjacente ainda existe. Portanto, "instalação + limpeza" deve ser concluída dentro do mesmo RUN, para que os arquivos temporários criados durante a instalação e a operação de exclusão estejam contidos na mesma camada.

P: apt-get clean por que reduz o tamanho da imagem? R: apt-get clean sozinho é quase ineficaz (o mesmo se aplica ao rm). Deve ser usado dentro do mesmo RUN: apt-get update && apt-get install && rm -rf /var/lib/apt/lists/*. --no-install-recommends é mais eficaz do que apt-get clean.

P: Como posso saber se o cache foi atingido? R: Na saída do docker build: Using cache indica um acerto, e Step 3/7 : RUN xxx indica uma reconstrução. Você também pode usar --progress=plain para visualizar logs detalhados. No CI, docker build --cache-from=app:latest permite reutilizar o cache do build anterior.

P: --no-cache Quando deve ser usado? R: Em dois cenários: ① Quando a imagem base requer uma atualização de segurança e precisa ser reconstruída; ② Quando um build falha e você suspeita que o cache está corrompido. Não use --no-cache durante o desenvolvimento diário, pois isso fará com que cada build comece do zero.

P: Como detectar vulnerabilidades de segurança nas imagens? R: docker scout quickview <imagem> (ferramenta oficial do Docker) ou trivy image <imagem> (código aberto). Trivy é a ferramenta de escaneamento de imagens de código aberto mais popular, capaz de detectar CVEs conhecidas em pacotes do SO e dependências de linguagem. Recomendamos integrar uma etapa de escaneamento no pipeline de CI.


📖 Resumo


📝 Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Use dive para analisar cada camada de uma imagem existente (como nginx:latest), anotando qual camada é a maior e quais arquivos foram adicionados.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Use hadolint para verificar os Dockerfiles das Aulas 7–9 e corrija todas as sugestões de nível warning.
  3. Desafio (Dificuldade: ⭐⭐⭐): Compare os tamanhos de imagem da mesma aplicação Python construída usando as imagens base alpine, debian e slim, e analise as razões das diferenças de tamanho.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%