A criação de imagens Docker eficientes é fundamental para pipelines de CI/CD ágeis e deployments rápidos. Imagens inchadas não apenas cnosomem mais armazenamento, mas também aumentam a superfície de ataque e o tempo de transferência de rede. A otimização de imagens foca em duas frentes principais: minimizar a quantidade de camadas (layers) e reduzir o tamanho real dos artefatos e dependências.
- Minimização de Camadas
Consolidação de Comandos
No sistema de camadas do Docker, apenas as instruções RUN, COPY e ADD geram novas camadas persistentes no sistema de arquivos. Outras diretivas como WORKDIR, ENV ou LABEL criam metadados intermediários que não impactam o tamanho final da imagem. Para evitar a criação de camadas redundantes, é uma prática essencial encadear comandos shell utilizando o operador lógico && e o caractere de continuação \.
Considere o seguinte exemplo onde instalamos utilitários de rede. Em vez de executar cada comando em uma linha separada, consolidamos a instalação e a limpeza do cache do gerenciador de pacotes em uma única instrução RUN:
FROM ubuntu:22.04
WORKDIR /opt/tools
LABEL maintainer="devops-team@example.com"
RUN apt-get update && \
apt-get install -y --no-install-recommends wget jq && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Ao utilizar a flag --no-install-recommends e limpar o diretório /var/lib/apt/lists/* na mesma camada, evitamos que o cache de índices e pacotes recomendados permaneçam no histórico da imagem, resultando em uma redução drástica de dezenas de megabytes.
Construção em Múltiplos Estágios (Multi-stage Builds)
Compilar código-fonte dentro da imagem final de execução é um erro comum que adiciona compiladores, bibliotecas de desenvolvimento e artefatos temporários ao pacote final. O recurso de multi-stage builds permite utilizar uma imagem pesada para compilação e copiar apenas o binário final para uma imagem de execução minimalista.
# Estágio 1: Compilação
FROM golang:1.21 AS builder
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o api-server main.go
# Estágio 2: Execução
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /src/api-server .
CMD ["./api-server"]
Neste cenário, a imagem final conterá apenas o binário estático e o sistema base Alpine, reduzindo o tamanho de centenas de megabytes para poucos kilobytes.
O Recurso de Squash
O Docker possui uma flag experimental --squash que comprime todas as camadas da imagem em uma única camada final. Embora reduza o tamanho do artefato, essa prática é fortemente desencorajada, pois destrói o mecanismo de cache e compartilhamento de camadas entre diferentes imagens, prejudicando o desempenho de pulls e pushes no registro.
- Redução do Tamanho Base e Artefatos
Seleção de Imagens Base Enxutas
A escolha da imagem base dita o tamanho mínimo da sua aplicação. Distribuições Linux completas contêm centenas de binários e bibliotecas que um container raramente utiliza. Substituir imagens padrão por variantes slim ou distribuições minimalistas como o Alpine Linux é o passo mais impactante.
FROM alpine:3.18
WORKDIR /opt/scripts
RUN apk add --no-cache curl ca-certificates bash
O Alpine Linux utiliza o musl libc e o gerenciador de pacotes apk, resultando em imagens base que frequentemente não ultrapassam os 5MB.
Gerenciamento do Contexto de Build
A instrução COPY . /app transfere todo o diretório de contexto local para a imagem. Se o diretório contiver pastas como node_modules, logs, artefatos de build ou o diretório .git, eles serão incluídos na camada, aumentando o tamanho e potencialmente vazando credenciais. A criação de um arquivo .dockerignore na raiz do projeto é obrigatória para excluir esses camihnos desnecessários antes que o contexto seja enviado para o daemon do Docker.
Limpeza de Artefatos na Mesma Camada
Devido à natureza imutável das camadas, excluir um arquivo em uma instrução RUN subsequente não remove o arquivo da camada anterior; ele apenas marca o arquivo como deletado na camada atual, mas o espaço em disco já foi alocado. A limpeza deve ocorrer dentro da mesma instrução onde o download ou instalação ocorreu.
WORKDIR /tmp
# Incorreto: O arquivo compactado permanece na camada anterior
# RUN wget https://example.com/release.tar.gz
# RUN tar -xzf release.tar.gz -C /opt/app && rm -f release.tar.gz
# Correto: Download, extração e remoção na mesma camada
RUN wget https://example.com/release.tar.gz && \
tar -xzf release.tar.gz -C /opt/app && \
rm -f release.tar.gz
Este princípio também se aplica a caches de gerenciadores de pacotes de linguagens, como pip cache purge para Python ou npm cache clean --force para Node.js, que devem ser executados no mesmo RUN da instalação das dependências.