Conteúdo
Tutorial de cache de build do Docker: otimize o tamanho das imagens
Com o mesmo Dockerfile, o tempo de build pode variar de menos de 1 segundo a vários minutos, dependendo de como você escreve as instruções. O tamanho da imagem também pode variar bastante. Nesta seção, você vai entender as regras do cache e aprender técnicas para reduzir o tamanho das imagens por meio de experimentos.
Como o cache funciona
docker build processa cada instrução em sequência. Para cada linha, o Docker verifica: esta instrução e todas as camadas anteriores são iguais às do último build?
- Iguais: usa o cache diretamente e exibe
CACHEDna saída. - Diferentes: executa esta linha novamente, e todas as linhas seguintes também deixam de usar o cache.
| Instrução | Critério de decisão |
|---|---|
RUN | Se a string do comando é igual (não verifica se os pacotes disponíveis na rede foram atualizados) |
COPY / ADD | Se o conteúdo dos arquivos copiados é igual |
| Outras | Se a instrução e os parâmetros são iguais |
Experimento: o impacto da ordem das instruções
Use um pequeno projeto com Express (o pacote express está em package.json) para comparar dois Dockerfiles.
Ordem incorreta (Dockerfile.bad):
FROM node:24-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
Ordem correta (Dockerfile.good):
FROM node:24-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
Ordem incorreta: altere uma linha de código
Faça um build e, em seguida, altere apenas uma palavra em server.js antes de fazer outro build:
$ docker build -f Dockerfile.bad -t cache-bad .
[+] Building 6.9s (9/9) FINISHED docker:default
=> [internal] load build definition from Dockerfile.bad 0.0s
=> => transferring dockerfile: 125B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/4] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/4] WORKDIR /app 0.0s
=> [3/4] COPY . . 0.1s
=> [4/4] RUN npm install 5.4s
=> exporting to image 1.0s
=> => exporting layers 0.5s
=> => exporting manifest sha256:d27475f082a53018e303541bc1c0691ac25526e87761300e2aa8ec76d73e 0.0s
=> => exporting config sha256:19f2024df4329a5f2af23e50d8c18826f073d425e18133e9571c393999d913 0.0s
=> => exporting attestation manifest sha256:58b4730e9566f2ec7e8222cd41c14ea971d87272b6b0c190 0.0s
=> => exporting manifest list sha256:58b8fc79124f2fe21270801d5e40f09839c06ba5203304423125270 0.0s
=> => naming to docker.io/library/cache-bad:latest 0.0s
=> => unpacking to docker.io/library/cache-bad:latest 0.4s
Como server.js foi alterado, [3/4] COPY . . foi executado novamente. Depois dele, [4/4] RUN npm install também foi executado outra vez e levou 5.4 segundos para baixar e instalar todos os pacotes novamente.
A ordem correta: altere uma linha de código
Primeiro build (é preciso executar npm install):
$ docker build -f Dockerfile.good -t cache-good .
[+] Building 5.7s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 95B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json ./ 0.0s
=> [4/5] RUN npm install 4.4s
=> [5/5] COPY . . 0.1s
=> exporting to image 0.9s
=> => exporting layers 0.5s
=> => exporting manifest sha256:4b36d92a774b02cc1a188193e178a445b90f330b19f2dcc741d631e246e8 0.0s
=> => exporting config sha256:4a597aa667d9ba52eab36c4483f7507bbcae158b1b22af5ca6e4ac5b7020fa 0.0s
=> => exporting attestation manifest sha256:efea2ef65e2672718391b6419b9f5f2be50396163032a29e 0.0s
=> => exporting manifest list sha256:236918100fd2092d0388da5c03b32dfa3abaa2e62304abb3e3dd335 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.4s
Faça outro build depois de alterar server.js:
$ docker build -f Dockerfile.good -t cache-good .
[+] Building 0.4s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package.json ./ 0.0s
=> CACHED [4/5] RUN npm install 0.0s
=> [5/5] COPY . . 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:6cd8c2671a295aab85724aefe72f8dc749f4d332224f5d534fff5e4e57fa 0.0s
=> => exporting config sha256:7df3d574ae91f98ea3634c4dcede63e64ce8018c902643c2db08c2b4dee4c3 0.0s
=> => exporting attestation manifest sha256:fc9ff7e57f4187c04b74446f5dd96e46ef9c7f0ea88372a6 0.0s
=> => exporting manifest list sha256:79e8a6d76ff2382f02bcadc183a44670959d1d0a30467a27b9d619e 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.0s
O build inteiro levou apenas 0.4 segundo: COPY package.json e RUN npm install aparecem como CACHED; somente o último COPY . . foi executado novamente. Este projeto tem apenas uma dependência; em projetos reais, que podem ter centenas de dependências, npm install pode levar vários minutos, e a diferença será ainda maior.
O mesmo princípio se aplica a todas as linguagens:
| Linguagem | Copie primeiro | Instale depois |
|---|---|---|
| Node.js | package.json, package-lock.json | npm ci |
| Python | requirements.txt ou pyproject.toml | pip install |
| Go | go.mod, go.sum | go mod download |
| Java (Maven) | pom.xml | mvn dependency:go-offline |
| PHP | composer.json, composer.lock | composer install |
Experimento: limpar arquivos temporários na mesma camada
Cada camada é sobreposta à anterior. Excluir arquivos em um RUN posterior não reduz o tamanho da camada anterior. Compare duas formas de instalar o curl:
Dockerfile.separate:
FROM debian:trixie-slim
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl
RUN rm -rf /var/lib/apt/lists/*
Dockerfile.same:
FROM debian:trixie-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
Compare os tamanhos após o build:
$ docker images --filter reference=apt-separate --filter reference=apt-same --filter reference=debian:trixie-slim
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
apt-same:latest 5357e9775c1e 136MB 34.9MB
apt-separate:latest ad6c0a37d9ea 174MB 51.7MB
O conteúdo é exatamente o mesmo (ambos instalaram o curl e removeram as listas de pacotes), mas apt-separate ficou 38 MB maior. Veja o motivo com docker history:
$ docker history apt-separate
IMAGE CREATED CREATED BY SIZE COMMENT
ad6c0a37d9ea 11 seconds ago RUN /bin/sh -c rm -rf /var/lib/apt/lists/* #… 20.5kB buildkit.dockerfile.v0
<missing> 11 seconds ago RUN /bin/sh -c apt-get install -y --no-insta… 13.4MB buildkit.dockerfile.v0
<missing> 16 seconds ago RUN /bin/sh -c apt-get update # buildkit 21.6MB buildkit.dockerfile.v0
<missing> 8 days ago # debian.sh --arch 'amd64' out/ 'trixie' '@1… 87.7MB debuerreotype 0.17
$ docker history apt-same
IMAGE CREATED CREATED BY SIZE COMMENT
5357e9775c1e 1 second ago RUN /bin/sh -c apt-get update && apt-get… 13.4MB buildkit.dockerfile.v0
<missing> 8 days ago # debian.sh --arch 'amd64' out/ 'trixie' '@1… 87.7MB debuerreotype 0.17
A camada de apt-get update em apt-separate tem 21,6 MB (as listas de pacotes baixadas). O rm -rf posterior apenas as “marca como excluídas” em uma nova camada; os 21,6 MB originais continuam na imagem. Quando tudo está no mesmo RUN, as listas de pacotes são removidas antes do fim dessa camada e nunca são armazenadas em nenhuma camada.
Formas comuns de não manter arquivos temporários em cada linguagem:
RUN pip install --no-cache-dir -r requirements.txt
RUN npm ci && npm cache clean --force
RUN apk add --no-cache curl
.dockerignore
No início do build, o Docker envia todo o contexto de build (geralmente a pasta atual) para o mecanismo de build. Sem excluir node_modules, o exemplo em Introdução ao Dockerfile envia 30 MB; depois da exclusão, apenas 159 B. Exemplo de .dockerignore:
.git
node_modules
dist
*.log
.env
.env.*
Dockerfile*
.dockerignore
Além de acelerar o build, isso evita que alterações nesses arquivos invalidem o cache de COPY . . e impede que .env e chaves privadas sejam incluídos na imagem.
Escolha uma imagem base enxuta
Tamanhos medidos das imagens Python 3.14 na seção Imagens:
| Imagem base | Espaço em disco |
|---|---|
python:3.14 | 1.63 GB |
python:3.14-slim | 180 MB |
python:3.14-alpine | 82.6 MB |
Build em vários estágios
Ferramentas de compilação e pacotes de desenvolvimento só são necessários durante o build e não devem permanecer na imagem final. O build em vários estágios é a maneira mais eficaz de reduzir o tamanho.
Montagem de cache: acelere o download de pacotes
O BuildKit oferece suporte a montagens de cache, que mantêm o cache de download dos gerenciadores de pacotes entre builds. Mesmo que o cache da camada RUN npm ci seja invalidado, não será necessário baixar todos os pacotes novamente pela rede:
# syntax=docker/dockerfile:1
FROM node:24-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
O conteúdo da montagem de cache não entra na imagem, portanto não aumenta o tamanho dela.
Forçar a desativação do cache
Quando você precisa obter a versão mais recente dos pacotes ou suspeita que há um problema com o cache:
$ docker build --no-cache -f Dockerfile.good -t cache-good .
[+] Building 6.6s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile.good 0.0s
=> => transferring dockerfile: 147B 0.0s
=> [internal] load metadata for docker.io/library/node:24-alpine 0.3s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 280B 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json ./ 0.0s
=> [4/5] RUN npm install 5.1s
=> [5/5] COPY . . 0.1s
=> exporting to image 1.0s
=> => exporting layers 0.4s
=> => exporting manifest sha256:30688d5c22069d97e9db0bd32d5939e8e99fda0c6a0c4af1f32fb5baacc9 0.0s
=> => exporting config sha256:e8e75108925e8f145e7367b8e36350a8175c3776154f560fb47a9fb287fe21 0.0s
=> => exporting attestation manifest sha256:adbbb2fc6683c9a35c67bb9237cf3afcfd24c39dd9aa4b55 0.0s
=> => exporting manifest list sha256:6b92b5704d4fa7a3f11cfda5ffde00e6516bdbf358e23dbece712a2 0.0s
=> => naming to docker.io/library/cache-good:latest 0.0s
=> => unpacking to docker.io/library/cache-good:latest 0.5s
--no-cache faz com que cada etapa seja executada novamente (sem nenhum CACHED). Com --pull, o Docker também verifica se há uma versão mais recente da imagem base.
Como visualizar e limpar o cache de build
O cache de build continua acumulando:
$ docker builder du
ID RECLAIMABLE SIZE LAST ACCESSED
mr6xd3ylof28pr8obxiet3fo8 true 403B 3 minutes ago
l34l5occgcgh8bscivg2iusrn true 626B 3 minutes ago
... (saída omitida)
Shared: 488.2MB
Private: 118.5MB
Reclaimable: 606.7MB
Total: 606.7MB
$ docker builder prune -f
ID RECLAIMABLE SIZE LAST ACCESSED
xqb9jtg0kmq0mdzb0vrmod68j true 20.67kB 3 minutes ago
u1pbndrekyh1q91qbb4n9e3fy* true 8.192kB 5 minutes ago
... (saída omitida)
Total: 118.5MB
docker builder prune exclui apenas o cache de build e não afeta imagens nem contêineres. Veja como limpar todos os dados do Docker em Liberar espaço em disco.
Resumo
- Quando uma camada muda, todas as camadas seguintes precisam ser reconstruídas.
- Coloque primeiro as etapas que mudam pouco (instalar pacotes) e depois as que mudam com frequência (copiar código).
- Baixe, instale e limpe os arquivos temporários no mesmo
RUNpara que eles não permaneçam na imagem. RUN --mount=type=cachepermite compartilhar os downloads de pacotes entre vários builds.
Exercícios
Exercício 1: Qual dos dois trechos de Dockerfile abaixo gera uma imagem menor? Por quê?
# A
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# B
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Resposta
B é menor. Em A, o rm é executado em uma nova camada, então as listas de pacotes continuam na camada anterior; em B, o download e a remoção acontecem na mesma camada, e as listas não são salvas.
Exercício 2: Construa o mesmo projeto uma vez com docker build --no-cache e outra sem essa opção e compare o tempo total exibido por [+] Building.
Dica
Sem a opção, a maioria das etapas mostra CACHED e geralmente leva apenas alguns décimos de segundo; com --no-cache, cada etapa é executada novamente, e o tempo depende da velocidade de instalação dos pacotes.
Perguntas frequentes
RUN apt-get update foi armazenado em cache e instalou uma versão antiga do pacote. O que fazer?
O cache compara apenas a string do comando; ele não verifica se os pacotes disponíveis na rede foram atualizados. Escreva apt-get update e apt-get install no mesmo RUN e, quando necessário, force uma nova compilação com docker build --no-cache.
O cache de build está ocupando muito espaço em disco?
Use docker builder du para consultar o espaço e docker builder prune para limpar. Veja a medição em [Liberar espaço em disco](/pt/docker/cleanup/).
Cada build no CI começa do zero e demora muito?
Use um cache externo, como cache-from: type=gha do GitHub Actions. Veja [Build automático com GitHub Actions](/pt/docker/ci-cd/).
Atualizado 14 de outubro de 2026