Dockerfile e cache de layers: builds rápidos de verdade
Objetivos desta aula
- Escrever Dockerfile ordenado para cache
- Usar .dockerignore
- Reduzir tamanho da imagem
Cada instrução do Dockerfile cria uma layer, e o Docker reaproveita a layer se a entrada não mudou. Daí a regra de ouro: copie primeiro os arquivos de dependência (package.json e lock), instale, e só depois copie o código. Invertendo essa ordem, qualquer alteração de uma linha de código invalida a instalação e o build volta a levar minutos.
.dockerignore é tão importante quanto o Dockerfile: sem ele, node_modules local, .git e arquivos .env vão para o contexto de build, deixando tudo lento e podendo vazar segredo dentro da imagem.
Escolha de base define tamanho e superfície de ataque. Alpine é minúscula mas usa musl (o que às vezes quebra binários nativos); as variantes slim de Debian são um bom meio; distroless entrega o mínimo para rodar, sem shell — excelente para produção.
1. Cada instrução é uma camada
FROM, RUN, COPY e ADD geram camadas. O Docker reaproveita uma camada do cache se a instrução e seus arquivos de entrada não mudaram. Quando uma camada muda, todas as seguintes são refeitas.
2. Ordem certa: do que muda pouco ao que muda muito
Copie primeiro package.json e package-lock.json, rode npm ci, e só depois copie o código. Assim, editar um arquivo .ts não reinstala dependências — o build cai de minutos para segundos.
3. .dockerignore
Sem ele, node_modules, .git e arquivos .env vão para o contexto de build: build lento, cache invalidado e segredo vazado na imagem.
4. BuildKit e cache mounts
RUN --mount=type=cache,target=/root/.npm npm ci mantém o cache do npm entre builds sem colocá-lo na imagem. No CI, --cache-from/--cache-to reaproveita camadas entre execuções.
5. CMD, ENTRYPOINT e forma exec
Use a forma JSON CMD ["node", "dist/server.js"]: o processo vira PID 1 e recebe SIGTERM. A forma shell (CMD node server.js) coloca um sh no meio que não repassa sinais.
6. Checagem final
Altere uma linha do código e rebuilde: apenas as últimas camadas devem ser refeitas.
Na prática
Dockerfile da API (ordem correta de cache)
dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -S app && adduser -S app -G app
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=app:app src ./src
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "src/server.js"]Nota de segurança: Nunca use ARG/ENV para segredo: valores ficam gravados no histórico da imagem e são legíveis por qualquer um com docker history.
.dockerignore
text
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
coverage
dist
Dockerfile
docker-compose*.yml
**/*.mdDockerfile otimizado para cache
dockerfile
# syntax=docker/dockerfile:1.7
FROM node:20.15-alpine
WORKDIR /app
COPY package.json package-lock.json ./ # muda raramente
RUN --mount=type=cache,target=/root/.npm npm ci # camada reaproveitada
COPY . . # muda sempre, fica por ultimo
RUN npm run build
CMD ["node", "dist/server.js"] # forma exec: recebe SIGTERM.dockerignore mínimo
text
node_modules
.git
.env*
dist
coverage
*.logNota de segurança: Nunca copie arquivos .env para a imagem: qualquer pessoa com acesso à imagem lê os segredos.
Por que isso importa
Build de 8 minutos vira 40 segundos com ordenação correta — impacto direto no tempo de entrega do time.
Erro comum
COPY . . antes de npm ci, destruindo o cache em toda alteração de código.
Dica de produção
Fixe a versão da base (node:22.11-alpine) em vez de tag móvel para builds reprodutíveis.
Alerta de segurança
Rode scan de vulnerabilidades na imagem no CI e atualize a base periodicamente.
Pergunta de entrevista
Como você diagnostica um build de Docker que ficou lento após uma mudança pequena?
Glossário
- contexto de build
- Conjunto de arquivos enviados ao daemon Docker durante o build.
- distroless
- Imagem sem shell nem gerenciador de pacotes, contendo só o runtime necessário.
- camada
- Diferença de filesystem gerada por uma instrução do Dockerfile.
- contexto de build
- Arquivos enviados ao Docker para construir a imagem.
- BuildKit
- Motor de build moderno do Docker, com cache mounts e builds paralelos.
Conexão com o CloudShop
Escrever o Dockerfile definitivo da API do CloudShop com usuário não-root.
Quiz da aula
1. Por que copiar package.json antes do código-fonte?
2. Por que copiar package.json antes do código?
3. Qual forma de CMD repassa SIGTERM corretamente?
Minhas anotações
Salvo automaticamente neste navegador.