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
- Estratégias para Otimizar o Número de Camadas da Imagem
- Dicas para Combinar Comandos RUN
- Limpeza de Dependências e Gerenciamento de Cache
- Usando Ferramentas de Lint para Dockerfile
- Desenvolvendo Métodos de Otimização de Cache
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.
# 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
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: ⭐⭐)
# 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.
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: ⭐⭐)
# 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 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.
# 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
▶ Exemplo: Verificando um Dockerfile com hadolint (Dificuldade: ⭐⭐)
hadolint é uma ferramenta de lint para Dockerfiles que verifica violações das melhores práticas.
# 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
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
# ============================================
# ANTES: Dockerfile não otimizado (1,2 GB)
# ============================================
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]
# ============================================
# 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 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"]
# 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 cleanpor que reduz o tamanho da imagem? R:apt-get cleansozinho é quase ineficaz (o mesmo se aplica aorm). 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 queapt-get clean.
P: Como posso saber se o cache foi atingido? R: Na saída do
docker build:Using cacheindica um acerto, eStep 3/7 : RUN xxxindica uma reconstrução. Você também pode usar--progress=plainpara visualizar logs detalhados. No CI,docker build --cache-from=app:latestpermite reutilizar o cache do build anterior.
P:
--no-cacheQuando 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-cachedurante 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) outrivy 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
- Essência da otimização de imagens: Reduzir o número de camadas + Limpar na mesma camada + Escolher uma imagem base menor
- Princípio de Combinação RUN: Combinar operações relacionadas em um único RUN; instalação e limpeza na mesma camada
- Otimização de cache: Colocar instruções que raramente mudam no início (COPY de arquivos de dependência), e as que mudam frequentemente no final (COPY de código fonte)
--no-install-recommendsReduz o volume de instalação em 30–50%- Use
divepara analisar a eficiência das camadas da imagem, ehadolintpara verificar as melhores práticas do Dockerfile - Processo completo de otimização: Imagem base Alpine → build multi-estágio → otimização de cache → não-root → verificação de saúde
📝 Exercícios
- Exercício Básico (Dificuldade: ⭐): Use
divepara analisar cada camada de uma imagem existente (comonginx:latest), anotando qual camada é a maior e quais arquivos foram adicionados. - 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.
- 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.