Olá! Na lição anterior, você aprendeu a otimizar drasticamente o tamanho das suas imagens Docker usando multi-stage builds. Vimos como um Dockerfile com múltiplos estágios separa as dependências de compilação das de execução. Cada estágio é composto por uma série de instruções (COPY, RUN, etc.).
Nesta lição, vamos investigar um mecanismo fundamental que o Docker usa para acelerar o processo de construção dessas imagens: o cache de camadas. O nosso objetivo é que você consiga descrever como o cache de camadas otimiza o processo de build de imagens e acelera builds subsequentes. Compreender este conceito é crucial, pois permite que você estruture seus Dockerfiles de forma inteligente para minimizar os tempos de espera durante o desenvolvimento, tornando seu fluxo de trabalho muito mais eficiente.
O que São as Camadas (Layers) do Docker?
Antes de falarmos sobre o cache, precisamos entender o que ele armazena: as camadas. Pense em uma imagem Docker não como um arquivo monolítico, mas como uma pilha de camadas somente leitura. Cada instrução em um Dockerfile (FROM, RUN, COPY, etc.) cria uma nova camada sobre a anterior.
Cada camada contém apenas as diferenças (um "diff") em relação à camada abaixo dela. Essa estrutura é muito semelhante à forma como o Git funciona, onde cada commit armazena as alterações em relação ao commit anterior. Uma imagem é o resultado da união de todas essas camadas.
O vídeo a seguir introduz essa ideia de forma muito clara.
How Dockerfile Layers/Caching Work
Para começar, assista a este trecho do vídeo 'How Dockerfile Layers/Caching Work' do canal Benjamin Porter. Ele explica como cada comando em um Dockerfile gera uma nova camada e como uma imagem é composta por essas camadas empilhadas.
Assista do timestamp 00:25 a 02:10. O ponto principal é entender que uma imagem é uma coleção de camadas e cada camada é resultado de uma instrução no Dockerfile.
O Mecanismo de Cache de Camadas
Agora que sabemos o que são as camadas, a ideia do cache se torna mais intuitiva. Quando você executa docker build, o Docker percorre as instruções do seu Dockerfile uma a uma. Para cada instrução, ele verifica: "Eu já executei esta instrução antes, em um contexto idêntico, e tenho uma camada armazenada para ela?".
- Se a resposta for sim, o Docker não executa a instrução novamente. Ele simplesmente reutiliza a camada existente do cache. Isso é um "cache hit".
- Se a resposta for não, o Docker executa a instrução, cria uma nova camada e a armazena no cache para uso futuro. A partir deste ponto, ocorre uma "invalidação de cache".
O ponto mais importante a entender é: uma vez que uma camada é invalidada, todas as camadas subsequentes no Dockerfile também são forçadas a serem reconstruídas, mesmo que suas instruções não tenham mudado.
A imagem a seguir ilustra perfeitamente esse processo em cadeia.

Para um aprofundamento sobre como o cache funciona e o que exatamente aciona uma invalidação, o texto a seguir é um excelente recurso.
The ultimate guide to Docker build cache - Depot
O artigo 'The ultimate guide to Docker build cache' do blog Depot oferece uma explicação detalhada sobre o funcionamento do cache de build.
Leia as seções 'How Docker build cache works' e 'Cache invalidation in-depth'. Preste atenção especial às diferentes regras de invalidação para instruções de metadados (como ENV), instruções de cópia de arquivos (COPY, ADD) e instruções de execução de comandos (RUN).
Como o artigo explica, a invalidação para um COPY é baseada no conteúdo dos arquivos (um checksum), enquanto a invalidação para um RUN é baseada apenas na string do comando em si. Essa distinção é fundamental para a otimização.
Otimizando seu Dockerfile para Cache
Com o conhecimento do mecanismo de cache, podemos estabelecer a regra de ouro para a otimização de Dockerfiles:
Ordene as instruções da menos provável de mudar para a mais provável de mudar.
No contexto de desenvolvimento de software, o que muda com mais frequência? O nosso código-fonte. E o que muda com menos frequência? As dependências do projeto.
Portanto, uma prática recomendada universal é:
- Copiar primeiro os arquivos que definem as dependências (ex:
*.csprojpara .NET,package.jsonpara Node.js,requirements.txtpara Python). - Executar o comando que instala essas dependências.
- Só então, copiar o restante do código-fonte da aplicação.
Essa estrutura garante que, quando você altera apenas uma linha no seu código, o Docker pode reutilizar a camada (frequentemente demorada) da instalação de dependências, tendo que reconstruir apenas as camadas finais que copiam e compilam/executam seu código.
No seu Dockerfile da lição anterior, nós já aplicamos instintivamente este princípio:
# ...
WORKDIR /app
# 1. Copia o arquivo de projeto (muda com pouca frequência)
COPY *.csproj .
# 2. Restaura as dependências (demorado, mas agora em cache)
RUN dotnet restore
# 3. Copia todo o resto (código-fonte, muda constantemente)
COPY . .
# ...
O vídeo a seguir demonstra de forma prática o impacto dramático que a ordem das instruções tem no tempo de build.
How Dockerfile Layers/Caching Work
Vamos voltar ao vídeo 'How Dockerfile Layers/Caching Work'. Desta vez, veja a demonstração prática de um Dockerfile não otimizado versus um otimizado.
Assista do timestamp 03:40 até o final (07:56). Observe como, no primeiro exemplo, uma simples mudança no código-fonte força a reinstalação de todas as dependências. Depois, veja como a reordenação das instruções resolve completamente o problema, tornando os builds subsequentes quase instantâneos.
Esta imagem resume a otimização para uma aplicação Node.js, mas o princípio é exatamente o mesmo que aplicamos para .NET.

Combinando Comandos para Otimizar Camadas
Uma outra técnica de otimização relacionada é agrupar múltiplos comandos RUN em uma única instrução usando o operador &&. Embora cada RUN crie uma camada, às vezes você quer que uma série de operações aconteça atomicamente dentro de uma única camada.
Isso tem duas vantagens principais:
- Redução do Tamanho da Imagem: Se você baixa um arquivo, o extrai e depois remove o arquivo compactado em três
RUNs separados, o arquivo compactado ainda existirá na primeira camada, mesmo que seja "deletado" na terceira. Combinar tudo em umRUNgarante que apenas o resultado final seja salvo na camada. - Segurança e Consistência: Evita que estados intermediários indesejados (como arquivos com segredos que são depois removidos) permaneçam "escondidos" em camadas intermediárias da imagem.
O vídeo a seguir explica bem este conceito.
Docker Image BEST Practices - From 1.2GB to 10MB
Este trecho do vídeo 'Docker Image BEST Practices' do canal Better Stack, explica por que apagar arquivos em camadas posteriores não reduz o tamanho da imagem e como combinar comandos em um único RUN resolve isso.
Assista do timestamp 03:53 a 05:35. Foque em entender o conceito de que as camadas são imutáveis e por que a combinação de comandos (&&) é importante tanto para o tamanho da imagem quanto para a segurança.
Teste seu entendimento!
Você tem o seguinte Dockerfile para uma aplicação Python com Flask. Atualmente, ele é muito ineficiente em termos de cache. Cada vez que você salva uma alteração em app.py, o demorado pip install é executado novamente.
# Dockerfile não otimizado
FROM python:3.9-slim
WORKDIR /app
# Copia todo o código e dependências de uma vez
COPY . .
# Instala as dependências
RUN pip install -r requirements.txt
# Expõe a porta e executa a aplicação
EXPOSE 5000
CMD ["python", "app.py"]
Como você reordenaria este Dockerfile para aproveitar ao máximo o cache de camadas do Docker?
Show answer
A versão otimizada separa a cópia e instalação das dependências da cópia do código-fonte da aplicação:
# Dockerfile otimizado
FROM python:3.9-slim
WORKDIR /app
# 1. Copia APENAS o arquivo de dependências
COPY requirements.txt .
# 2. Instala as dependências. Esta camada será cacheada na maioria das vezes.
RUN pip install -r requirements.txt
# 3. Agora copia o resto do código da aplicação
COPY . .
# Expõe a porta e executa a aplicação
EXPOSE 5000
CMD ["python", "app.py"]
Com esta estrutura, se você alterar apenas o app.py e reconstruir a imagem, as camadas FROM, WORKDIR, COPY requirements.txt . e RUN pip install serão todas reutilizadas do cache. O Docker só precisará executar a última instrução COPY e criar uma nova imagem, o que é um processo quase instantâneo.
Conclusão
Parabéns! Você agora entende um dos conceitos mais importantes para um fluxo de trabalho de desenvolvimento ágil com Docker.
Principais pontos a reter:
- Camadas Imutáveis: Cada instrução em um
Dockerfilecria uma camada somente leitura, que é um "diff" da camada anterior. - Invalidação em Cascata: O Docker reutiliza camadas do cache até encontrar uma instrução que mudou. A partir desse ponto, essa camada e todas as seguintes são reconstruídas.
- A Ordem Importa: Para maximizar o uso do cache, organize seu
Dockerfiledas instruções que mudam com menos frequência (como instalação de dependências) para as que mudam com mais frequência (como a cópia do seu código-fonte). - Agrupamento de Comandos: Use
&¶ combinar múltiplos comandos em uma única instruçãoRUN, o que pode reduzir o tamanho final da imagem e melhorar a segurança.
Até agora, usamos o comando docker build de forma um tanto superficial. Na próxima lição, vamos nos aprofundar nele. Você irá construir uma imagem Docker a partir de um Dockerfile usando docker build e explicar o papel do contexto de build (build context). Entender o contexto é fundamental, pois ele define quais arquivos do seu host estão disponíveis para o processo de build e tem um impacto direto no desempenho e no cache.