Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 13

Building Docker Images: Dockerfile and Build Context

Recommended resources

Olá! Na lição anterior, mergulhamos no funcionamento do cache de camadas do Docker e vimos como a ordenação correta das instruções em um Dockerfile pode acelerar drasticamente os builds subsequentes. Essa otimização é um dos segredos para um fluxo de desenvolvimento ágil e eficiente.

Nesta lição, vamos focar no comando que inicia todo esse processo: docker build. Nosso objetivo é que você seja capaz de construir uma imagem Docker a partir de um Dockerfile usando docker build e explicar o papel do contexto de build (build context). Compreender o que é o contexto de build e como ele funciona internamente é fundamental, pois ele dita quais arquivos do seu ambiente de desenvolvimento estão disponíveis para a construção da imagem, impactando diretamente o desempenho, a segurança e a corretude do processo.

O Comando docker build

Até agora, usamos o comando docker build de forma bastante direta. Vamos formalizar seu uso mais comum. A sintaxe básica é docker build [OPTIONS] PATH.

  • docker build: O comando em si.
  • [OPTIONS]: Flags que modificam o comportamento do comando. A mais comum e importante é -t (ou --tag), que permite nomear e etiquetar sua imagem em um formato nome:tag (por exemplo, meu-app:1.0).
  • PATH: Um caminho para um diretório no seu sistema de arquivos. Este caminho especifica o contexto de build.

O vídeo a seguir demonstra o uso prático deste comando.

Dockerfile Tutorial - Docker in Practice || Docker Tutorial 10

Para começar, assista a este trecho do vídeo 'Dockerfile Tutorial' do canal TechWorld with Nana. Ela demonstra como usar docker build com a flag -t para construir e nomear uma imagem, e introduz o . como o caminho para o Dockerfile (e o contexto).

Assista do timestamp 11:46 a 13:16. O foco é entender a sintaxe básica: docker build -t <nome>:<tag> <caminho_do_contexto>.

No exemplo docker build -t my-app:1.0 ., o . no final é crucial. Ele diz ao Docker: "use o diretório atual como o contexto de build". Mas o que isso significa exatamente?


O que é o Contexto de Build?

O contexto de build é o conjunto de arquivos e diretórios no PATH especificado que são enviados ao daemon do Docker. Uma vez que o daemon tem acesso a esse "pacote" de arquivos, as instruções no seu Dockerfile, como COPY e ADD, podem referenciá-los para incluí-los na imagem.

Pense no Dockerfile como uma receita de bolo e no contexto de build como todos os ingredientes que você colocou na sua bancada. A receita só pode usar os ingredientes que estão na bancada.

A imagem a seguir ilustra o fluxo completo, destacando onde o contexto se encaixa.

Docker Build Workflow
Esta imagem mostra o fluxo de trabalho do Docker. O `Dockerfile` (as instruções) e o `Build context` (os arquivos disponíveis) são processados pelo motor de build para criar uma imagem, que por sua vez é usada para executar um contêiner.

É fundamental entender a importância do contexto por três razões:

  1. Funcionalidade: Sua instrução COPY ./app /app só funcionará se um diretório chamado app existir dentro do contexto de build.
  2. Performance: Antigamente, todo o conteúdo do contexto era enviado para o daemon Docker no início do build. Um contexto grande (com centenas de megabytes de arquivos desnecessários) tornava o início do build lento.
  3. Segurança: Incluir arquivos sensíveis (como chaves SSH, arquivos .env com senhas) no contexto de build cria o risco de que eles sejam acidentalmente copiados para a imagem final.

Para solidificar essa definição, o artigo a seguir oferece uma explicação concisa e clara.

Docker Build Tutorial: What is a Build Context?

Leia esta seção do artigo 'Docker Build Tutorial' do freeCodeCamp para obter uma definição formal do contexto de build e entender por que ele é tão importante.

Leia a seção 'What is a Build Context?'. Foque nos quatro pontos listados: Security, Performance, Functionality, e Efficiency.


Como o Contexto de Build Funciona Internamente

Dado seu interesse em entender "como as coisas funcionam", vamos aprofundar um pouco. O Docker opera em uma arquitetura cliente-servidor. O comando docker que você executa no terminal é o cliente. Ele se comunica com o daemon do Docker (o servidor, dockerd), que é o processo em segundo plano que gerencia contêineres, imagens, etc.

Quando você executa docker build, o cliente Docker é responsável por empacotar o contexto de build e enviá-lo para o daemon.

No entanto, as versões modernas do Docker usam um subsistema de build chamado BuildKit. O BuildKit introduziu uma otimização massiva: em vez de o cliente enviar todo o contexto de uma vez, o BuildKit (que roda no lado do daemon) analisa o Dockerfile e solicita ao cliente apenas os arquivos de que precisa, quando precisa deles.

Por exemplo, para a instrução COPY package.json ., o BuildKit pedirá apenas o arquivo package.json. Mais tarde, para a instrução COPY src/ ./src/, ele pedirá o conteúdo do diretório src. Isso é muito mais eficiente e é um dos motivos pelos quais os builds modernos são mais rápidos.

O artigo a seguir detalha essa arquitetura de forma excelente.

Docker Build Tutorial: Docker Build Architecture

Esta seção do mesmo artigo do freeCodeCamp explora a arquitetura por trás do docker build, explicando os papéis do cliente (Buildx) e do servidor (BuildKit) e como a transferência de contexto sob demanda funciona.

Leia a seção 'Docker Build Architecture: How It All Works'. Preste atenção especial na subseção 'How They Communicate' e no diagrama que ilustra a transferência de arquivos sob demanda. Isso é o cerne do funcionamento moderno do build.


Gerenciando o Contexto com .dockerignore

Então, como garantimos que nosso contexto de build seja enxuto e seguro, contendo apenas o que é estritamente necessário? A resposta é o arquivo .dockerignore.

Funcionando de maneira muito semelhante a um arquivo .gitignore, o .dockerignore permite que você liste arquivos e diretórios a serem excluídos do contexto de build antes que ele seja enviado ao daemon do Docker.

Para um projeto .NET, um .dockerignore típico excluiria os diretórios bin/ e obj/, arquivos de configuração de usuário como .vs/, e talvez logs ou outros artefatos locais.

A imagem a seguir visualiza perfeitamente o papel do .dockerignore.

Build Context and .dockerignore File
Esta ilustração demonstra como um arquivo `.dockerignore` dentro de uma pasta de projeto especifica quais arquivos e pastas devem ser excluídos do contexto de build antes que o processo de construção da imagem Docker seja iniciado.

A sintaxe é direta, suportando wildcards (*) e negações (!). Para uma referência completa sobre a sintaxe e o comportamento, a documentação oficial é o melhor recurso.

Build context: .dockerignore files

A documentação oficial do Docker detalha tudo o que você precisa saber sobre o arquivo .dockerignore, incluindo sua sintaxe, regras de correspondência e precedência.

Leia a seção '.dockerignore files'. Não é preciso memorizar tudo, mas entenda o propósito principal, a sintaxe básica (comentários, wildcards) e como as exceções com ! funcionam.


Contexto de Build: Um Erro Comum

Um dos erros mais comuns ao começar é executar docker build do diretório errado. Lembre-se, o Dockerfile pode apenas usar COPY para arquivos que estão dentro do contexto.

Imagine a seguinte estrutura:

/workspace/
├── my-dotnet-api/
│   ├── Controllers/
│   ├── MyApi.csproj
│   └── Dockerfile
└── shared-library/
    └── Shared.csproj

Se o seu Dockerfile em my-dotnet-api/ precisa do Shared.csproj, e você está no diretório my-dotnet-api/ e executa:

docker build .

O contexto é my-dotnet-api/. Uma instrução como COPY ../shared-library/Shared.csproj . falhará, porque ../shared-library/ está fora do contexto.

A solução é executar o build a partir do diretório /workspace/, ajustando o comando para apontar para o Dockerfile correto:

docker build -f my-dotnet-api/Dockerfile .

Neste caso:

  • -f my-dotnet-api/Dockerfile: Especifica o caminho para o seu Dockerfile.
  • .: Define o contexto de build como o diretório atual (/workspace/), que agora inclui tanto my-dotnet-api/ quanto shared-library/.
Teste seu entendimento!

Você tem um projeto de API .NET com a seguinte estrutura de arquivos:

/my-awesome-api/
├── src/
│   └── My.Api/
│       ├── Controllers/
│       │   └── WeatherForecastController.cs
│       ├── My.Api.csproj
│       ├── Program.cs
│       └── appsettings.json
├── test/
│   └── My.Api.Tests/
│       └── ... (arquivos de teste)
├── .dockerignore
├── .gitignore
├── Dockerfile
└── my-awesome-api.sln

O Dockerfile foi escrito para ser executado a partir do diretório raiz /my-awesome-api/.

  1. Qual comando você executaria, e de qual diretório, para construir a imagem my-api:1.0?
  2. Quais diretórios você definitivamente incluiria no seu arquivo .dockerignore para otimizar o contexto de build?
Show answer
  1. Você deve estar no diretório raiz do projeto, /my-awesome-api/. O comando correto seria:

    docker build -t my-api:1.0 .
    

    O . no final é fundamental, pois define /my-awesome-api/ como o contexto de build, dando ao Dockerfile acesso a tudo o que ele precisa (o diretório src, o arquivo .sln, etc.).

  2. Um bom arquivo .dockerignore para um projeto .NET deve excluir, no mínimo, os artefatos de build, configurações de IDE e diretórios de teste.

    # Artefatos de build do .NET
    **/bin/
    **/obj/
    
    # Arquivos de IDE (ex: Visual Studio)
    .vs/
    
    # Diretório de testes, não é necessário na imagem final da API
    test/
    
    # Arquivos de controle de versão (embora geralmente já sejam ignorados, é bom ser explícito)
    .git/
    .gitignore
    
    # O próprio .dockerignore não precisa entrar na imagem
    .dockerignore
    Dockerfile
    

    Excluir bin e obj é crucial para evitar que artefatos de compilação locais sejam copiados para a imagem. Excluir test/ mantém a imagem de produção enxuta e focada apenas no código da aplicação.


Conclusão

Excelente! Agora você tem uma compreensão profunda de um dos conceitos mais fundamentais do Docker. Saber como o docker build funciona e como gerenciar seu contexto de build é uma habilidade essencial para criar imagens de forma eficiente e segura.

Principais pontos a reter:

  • O comando docker build usa um Dockerfile e um contexto de build para criar uma imagem.
  • O contexto de build é o conjunto de arquivos enviados ao daemon do Docker, definindo o que está disponível para as instruções COPY e ADD.
  • O caminho para o contexto é o último argumento do comando docker build (geralmente .).
  • O BuildKit, o moderno motor de build do Docker, otimiza o processo solicitando arquivos do contexto sob demanda, em vez de transferir tudo de uma vez.
  • Use um arquivo .dockerignore para excluir arquivos desnecessários (artefatos de build, logs, diretórios de teste, arquivos sensíveis) do contexto, melhorando a velocidade e a segurança.

Na lição anterior, discutimos como cada instrução em um Dockerfile cria uma camada. Na próxima lição, vamos aprender a inspecionar essas camadas. Você irá analisar as camadas e o tamanho da imagem usando docker history, o que nos permitirá ver "por dentro" da imagem que acabamos de construir e entender melhor sua composição.