Pular para o conteúdo

Reduzir o tamanho de uma imagem Docker

Intermediário14 min de leituracontainers

Imagem grande custa em três lugares: tempo de push e pull em cada deploy, armazenamento no registry, e superfície de ataque — cada pacote instalado é um CVE em potencial. Este guia percorre as reduções na ordem de retorno, medindo antes e depois.

Janela do terminal
docker images loja --format '{{.Size}}'
docker history loja:atual --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' | head -20

docker history mostra o peso por camada e quase sempre aponta o culpado na primeira linha: a imagem base, o gerenciador de pacotes, ou um COPY que trouxe o repositório inteiro.

Para uma análise mais detalhada, o dive mostra o desperdício por camada:

Janela do terminal
dive loja:atual

O maior ganho isolado, e o mais fácil.

Base Ordem de grandeza Quando usar
ubuntu, debian ~75–120 MB precisa de muitas ferramentas do sistema
-slim ~30–80 MB bom padrão para linguagens interpretadas
alpine ~5–15 MB precisa de shell; atenção ao musl
distroless ~2–20 MB binário compilado; sem shell nem gerenciador
scratch 0 binário estático (Go, Rust)

Duas ressalvas honestas: Alpine usa musl em vez de glibc, o que pode mudar o comportamento de DNS e quebrar bibliotecas nativas — e, em Python, forçar compilação de pacotes que teriam wheel pronto, aumentando o tempo de build. Distroless não tem shell, o que é ótimo para segurança e exige kubectl debug com container efêmero para diagnosticar.

A imagem final não precisa do compilador, das ferramentas de teste nem das dependências de desenvolvimento.

# ---- build ----
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download # camada cacheável: só muda se as deps mudarem
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/loja ./cmd/loja
# ---- runtime ----
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /bin/loja /bin/loja
USER nonroot:nonroot
ENTRYPOINT ["/bin/loja"]

Para linguagens interpretadas, o mesmo princípio: instale dependências em um estágio e copie só o que o runtime precisa.

FROM python:3.13-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/instalado -r requirements.txt
FROM python:3.13-slim
COPY --from=build /instalado /usr/local
COPY app/ /app/
WORKDIR /app
USER 10001
CMD ["python", "-m", "app"]

Cada instrução cria uma camada. Apagar em uma camada seguinte não reduz o tamanho final — o dado continua na camada anterior.

# errado: o cache do apt continua na imagem
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# certo: instala e limpa na mesma camada
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

Equivalentes por ecossistema: --no-cache no apk, --no-cache-dir no pip, npm ci --omit=dev seguido de npm cache clean --force.

Sem ele, o contexto inteiro vai para o daemon — inclusive .git, node_modules local e artefatos de teste. Isso incha a imagem e deixa o build lento.

.git
node_modules
dist
*.log
.env
.terraform
tests/fixtures/grandes

O .env na lista não é detalhe estético: segredo copiado para uma camada permanece na imagem mesmo que você o apague depois.

Janela do terminal
docker build -t loja:novo .
docker images loja --format '{{.Tag}}\t{{.Size}}'
docker history loja:novo --format '{{.Size}}\t{{.CreatedBy}}' | head -10
# e confirme que continua funcionando — o objetivo não é a menor imagem, é a menor que roda
docker run --rm loja:novo --version
trivy image --severity HIGH,CRITICAL loja:novo # a redução de CVEs costuma ser grande

Meça também o tempo de build: multi-stage bem ordenado costuma acelerar, mas trocar a base pode desacelerar (compilação de dependências). Se o build ficou muito mais lento, veja cache de build no CI.

Sintoma Causa provável
exec format error Binário compilado para outra arquitetura
not found ao rodar binário existente Binário dinâmico em scratch/distroless — compile estático
Erro de DNS ou de SSL só no Alpine musl, ou faltam ca-certificates
permission denied Usuário não-root sem permissão no caminho; ajuste no build
Imagem menor, mas build muito mais lento Ordem do Dockerfile invalidando o cache
  • Tamanho medido antes e depois.
  • Base mínima adequada à stack.
  • Multi-stage separando build de runtime.
  • Limpeza feita na mesma camada da instalação.
  • .dockerignore cobrindo .git, dependências locais e segredos.
  • Container executando como usuário não-root.
  • Aplicação testada na imagem nova.