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 CACHED na saída.
  • Diferentes: executa esta linha novamente, e todas as linhas seguintes também deixam de usar o cache.
InstruçãoCritério de decisão
RUNSe a string do comando é igual (não verifica se os pacotes disponíveis na rede foram atualizados)
COPY / ADDSe o conteúdo dos arquivos copiados é igual
OutrasSe a instrução e os parâmetros são iguais
Rebuild após alterar o código: instalar os pacotes primeiro permite que npm ci use o cache; copiar o código-fonte primeiro faz npm ci ser executado novamente
Quando uma camada muda, todas as camadas abaixo dela precisam ser reconstruídas: coloque as etapas que mudam com menos frequência no início

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:

LinguagemCopie primeiroInstale depois
Node.jspackage.json, package-lock.jsonnpm ci
Pythonrequirements.txt ou pyproject.tomlpip install
Gogo.mod, go.sumgo mod download
Java (Maven)pom.xmlmvn dependency:go-offline
PHPcomposer.json, composer.lockcomposer 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 baseEspaço em disco
python:3.141.63 GB
python:3.14-slim180 MB
python:3.14-alpine82.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 RUN para que eles não permaneçam na imagem.
  • RUN --mount=type=cache permite 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