Layers e cache no build do Docker
1. Entendendo Layers no Docker
Cada instrução em um Dockerfile gera uma camada imutável (layer). Quando você executa docker build, o Docker processa cada comando sequencialmente e cria uma nova camada sobre a anterior. Essas camadas são armazenadas no sistema de arquivos union (OverlayFS, AUFS) e empilhadas para formar a imagem final.
FROM ubuntu:22.04 # Layer 1: base image
RUN apt-get update # Layer 2: atualiza pacotes
RUN apt-get install -y python3 # Layer 3: instala Python
COPY app.py /app/ # Layer 4: adiciona código
CMD ["python3", "/app/app.py"] # Layer 5: comando final
Cada layer é apenas a diferença em relação à anterior. Isso permite reuso eficiente: se você tem 10 imagens baseadas em Ubuntu, as layers do Ubuntu são compartilhadas entre elas.
2. Mecanismo de Cache no Build
O cache de build funciona através de hash das instruções. Quando você executa docker build, o Docker calcula um hash para cada instrução baseado em:
- Conteúdo da instrução (comando exato)
- Arquivos copiados (hash do conteúdo)
- Contexto de build
- Layers anteriores
# Exemplo de hit de cache
$ docker build -t myapp:latest .
Step 1/5 : FROM ubuntu:22.04
---> a1b2c3d4e5f6
Step 2/5 : RUN apt-get update
---> Using cache
---> b2c3d4e5f6a1
O cache é invalidado quando:
- A instrução muda
- Arquivos copiados mudam
- A ordem das instruções muda
- O contexto de build muda
3. Boas Práticas para Otimizar o Cache
A regra de ouro: ordene as instruções do menos volátil para o mais volátil.
# RUIM: cache quebrado frequentemente
FROM node:18
COPY . /app # Qualquer mudança no código invalida todo o cache
RUN npm install # Precisa reinstalar dependências
RUN npm run build
# BOM: otimizado para cache
FROM node:18
WORKDIR /app
COPY package.json package-lock.json ./ # Dependências primeiro
RUN npm install # Cache hit se package.json não mudou
COPY . . # Só o código muda
RUN npm run build # Rebuild rápido
Combine comandos RUN para reduzir número de layers:
# RUIM: muitas layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*
# BOM: uma layer otimizada
RUN apt-get update && \
apt-get install -y curl git && \
rm -rf /var/lib/apt/lists/*
4. Estratégias com COPY e ADD
O .dockerignore é fundamental para evitar que arquivos desnecessários invalidem o cache:
# .dockerignore
node_modules/
.git/
*.log
.env
dist/
coverage/
Copie arquivos essenciais antes do código completo:
# Para aplicações Node.js
FROM node:18
WORKDIR /app
COPY package.json package-lock.json ./ # Cache de dependências
RUN npm install --production
COPY src/ ./src/ # Cache de código
COPY public/ ./public/
Timestamps e permissões também afetam o cache. O Docker usa o conteúdo dos arquivos, não o timestamp, mas mudanças de permissão podem invalidar.
5. Gerenciamento de Contexto de Build
O contexto de build (docker build .) inclui todos os arquivos no diretório atual. Arquivos não utilizados geram overhead e podem invalidar cache.
# Enviando contexto grande para o daemon
$ docker build -t myapp .
Sending build context to Docker daemon 1.2GB # Muito grande!
# Com .dockerignore eficiente
Sending build context to Docker daemon 15MB # Aceitável
Técnicas avançadas:
# Build em subdiretório específico
$ docker build -f docker/Dockerfile -t myapp ./src
# Uso de --target para builds multi-estágio
$ docker build --target builder -t myapp:builder .
$ docker build --target runtime -t myapp:latest .
6. Multi-stage Builds e Cache
Multi-stage builds permitem cache granular entre estágios:
# Dockerfile para aplicação Go
# Estágio 1: builder
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # Cache de dependências
COPY . .
RUN CGO_ENABLED=0 go build -o app .
# Estágio 2: runtime (imagem final pequena)
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/app /app/app
CMD ["/app/app"]
O cache do estágio builder é preservado mesmo que o runtime mude, e vice-versa. Isso é especialmente útil em CI/CD onde os estágios de compilação são caros.
7. Cache Remoto e CI/CD
Em pipelines CI/CD, o cache local não persiste entre builds. Use --cache-from para reutilizar cache de registries:
# Pipeline Jenkins/GitLab CI
# 1. Puxar imagem anterior como cache
docker pull registry.example.com/myapp:latest || true
# 2. Build com cache da imagem remota
docker build \
--cache-from registry.example.com/myapp:latest \
-t registry.example.com/myapp:latest .
# 3. Push da nova imagem
docker push registry.example.com/myapp:latest
Para cache distribuído em equipes:
# Usando buildx com cache remoto
docker buildx build \
--cache-from type=registry,ref=registry.example.com/myapp:cache \
--cache-to type=registry,ref=registry.example.com/myapp:cache,mode=max \
-t registry.example.com/myapp:latest .
8. Diagnóstico e Troubleshooting
Inspecione layers para entender o que está consumindo espaço:
# Ver histórico de layers
docker history myapp:latest
IMAGE CREATED CREATED BY SIZE
a1b2c3d4e5f6 2 hours ago CMD ["python3" "/app/app.py"] 0B
b2c3d4e5f6a1 2 hours ago COPY app.py /app/ 5kB
c3d4e5f6a1b2 2 hours ago RUN apt-get install -y python3 150MB
d4e5f6a1b2c3 2 hours ago RUN apt-get update 50MB
e5f6a1b2c3d4 2 hours ago FROM ubuntu:22.04 77MB
# Inspecionar detalhes de uma imagem
docker inspect myapp:latest
Ferramentas auxiliares:
# dive - análise visual de layers
dive myapp:latest
# hadolint - lint para Dockerfile
hadolint Dockerfile
# buildx - debug de cache
docker buildx build --no-cache-filter=myapp -t myapp:latest .
Para identificar quebra de cache, analise os logs de build:
$ docker build -t myapp:latest .
Step 1/5 : FROM node:18
---> abc123
Step 2/5 : WORKDIR /app
---> Using cache
Step 3/5 : COPY package.json ./
---> 9f8e7d6c5b4a # Cache miss! package.json mudou
Step 4/5 : RUN npm install
---> Running in 1a2b3c4d5e6f # Precisa reinstalar
Referências
- Docker Build Cache Documentation — Documentação oficial sobre cache de build, incluindo estratégias avançadas e cache remoto
- Docker Best Practices for Writing Dockerfiles — Guia oficial de boas práticas para Dockerfiles com foco em layers e cache
- Multi-stage Builds Documentation — Documentação oficial sobre builds multi-estágio e otimização de cache entre estágios
- dive: Tool for Exploring Layers — Ferramenta open-source para inspecionar e analisar layers de imagens Docker
- hadolint: Dockerfile Linter — Linter para Dockerfiles que ajuda a identificar problemas de cache e boas práticas
- Buildx Cache Backends — Documentação sobre backends de cache para buildx, incluindo registry, local e inline
- Kubernetes Pod Lifecycle and Container Caching — Documentação do Kubernetes sobre lifecycle de pods e como o cache de imagens afeta a inicialização