Olá! Na lição anterior, você aprendeu a integrar qualquer dependência containerizada arbitrária usando o método AddContainer, contanto que ela estivesse disponível como uma imagem pré-construída em um registro como o Docker Hub. Isso abriu um leque de possibilidades para orquestrar serviços que não possuem um componente Aspire dedicado.
No entanto, e se a dependência que você precisa adicionar não for uma imagem pública? E se for um serviço legado da sua empresa, um microserviço em outra linguagem (como Go ou Python), ou mesmo uma versão personalizada de um serviço conhecido, que só existe como um Dockerfile no seu repositório?
Nesta lição, vamos exatamente abordar esse cenário. Nosso objetivo é adicionar dependências construídas a partir de um Dockerfile a uma aplicação Aspire usando o método WithDockerfile. Você descobrirá como instruir o Aspire a construir uma imagem a partir do seu código-fonte local como parte do processo de inicialização, oferecendo a máxima flexibilidade para definir seu ambiente de desenvolvimento.
De Imagens Prontas para Construções Locais
Quando você usa builder.AddContainer("my-service", "some/image:tag"), o Aspire (através do Developer Control Plane) executa o equivalente a um docker pull seguido por um docker run. O ponto de partida é uma imagem que já existe.
Para trabalhar com Dockerfiles locais, o Aspire fornece dois métodos principais: AddDockerfile e WithDockerfile. Embora pareçam semelhantes, eles servem a propósitos distintos e é crucial entender a diferença.
Add Dockerfiles to your app model
A documentação oficial do Aspire explica a diferença entre esses dois métodos de forma clara. Esta leitura é fundamental para entender quando usar cada um.
Leia a introdução e a seção "When to use AddDockerfile vs WithDockerfile". Foque em entender os cenários de uso para cada método. Um cria um novo recurso genérico, enquanto o outro personaliza um existente.
Resumindo a leitura:
-
AddDockerfile(name, contextPath): Use este método quando você quer adicionar um novo serviço que é definido exclusivamente por umDockerfile. Ele não tem nenhuma funcionalidade específica do Aspire associada. O resultado é umIResourceBuilder<ContainerResource>genérico. É o equivalente a criar um contêiner do zero a partir de umDockerfile. -
WithDockerfile(contextPath): Use este método para personalizar um recurso de contêiner já existente. Você começa combuilder.AddContainer(...)ou um componente Aspire comobuilder.AddPostgres(...)e, em seguida, encadeia.WithDockerfile(...). Isso diz ao Aspire: "Em vez de usar a imagem padrão para este recurso, construa uma a partir deste Dockerfile". A grande vantagem é que o recurso mantém seu tipo original (ex:PostgresServerResource) e toda a sua funcionalidade associada, como a injeção de connection strings formatadas e métodos de extensão específicos (.WithPgAdmin()).
Como nosso objetivo hoje é integrar e personalizar dependências, focaremos no poderoso método WithDockerfile.
Aprofundando no WithDockerfile
O método WithDockerfile é a ponte entre a conveniência dos componentes Aspire e a flexibilidade das construções Docker personalizadas. Ele permite que você use os componentes de primeira classe do Aspire como base e apenas altere a "receita" do contêiner subjacente.
Vamos ver um exemplo prático. Suponha que você precise de um servidor web Nginx para servir arquivos estáticos, mas você quer incluir um arquivo de configuração personalizado e o conteúdo do site diretamente na imagem.
1. A Estrutura do Projeto e o Dockerfile
Primeiro, vamos criar um diretório para nossa configuração do Nginx no projeto AppHost, por exemplo: AppHost/nginx-custom/. Dentro dele, colocamos nosso Dockerfile e os arquivos necessários.
AppHost/nginx-custom/Dockerfile:
# Usa a imagem oficial do Nginx como base
FROM nginx:1.25
# Copia nossa configuração personalizada para o local correto dentro do contêiner
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
# Copia o conteúdo do nosso site para o diretório raiz do Nginx
COPY ./html /usr/share/nginx/html
AppHost/nginx-custom/nginx.conf:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
E em AppHost/nginx-custom/html/, você teria seu index.html.
2. A Configuração no AppHost
Agora, no Program.cs do seu AppHost, você pode adicionar este serviço. A abordagem é começar definindo um contêiner genérico e depois especificar que ele deve ser construído a partir do nosso Dockerfile.
// Em AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// Adiciona um recurso de contêiner. O nome da imagem aqui ("nginx") é mais um
// placeholder, pois será substituído pela imagem construída localmente.
var webServer = builder.AddContainer("my-custom-nginx", "nginx")
.WithDockerfile("./nginx-custom") // Diz ao Aspire para construir a partir deste diretório de contexto
.WithHttpEndpoint(port: 8080, targetPort: 80); // Expõe a porta do Nginx
builder.Build().Run();
Quando você executa o AppHost, o Aspire irá:
- Detectar a chamada
.WithDockerfile("./nginx-custom"). - Invocar o
docker buildusandoAppHost/nginx-customcomo o contexto de build. - Criar uma imagem Docker localmente com um nome gerado dinamicamente.
- Executar um contêiner a partir desta imagem recém-construída, em vez de puxar
nginxdo Docker Hub. - Mapear a porta 8080 do seu host para a porta 80 do contêiner, como solicitado.
A documentação da API fornece os detalhes técnicos sobre os parâmetros que você pode usar.
ContainerResourceBuilderExtensions.WithDockerfile Method
A documentação da API para o método WithDockerfile detalha seus parâmetros e o que ele retorna. É uma referência útil para entender as opções disponíveis.
Examine as seções "Definition", "Parameters", e "Examples". Preste atenção nos parâmetros contextPath, dockerfilePath, e stage. O stage é particularmente útil se você estiver usando multi-stage builds em seu Dockerfile, um padrão comum para otimizar imagens que você, como desenvolvedor experiente, certamente conhece.
O Verdadeiro Poder: Personalizando Componentes Aspire
O exemplo do Nginx é bom, mas o cenário onde WithDockerfile realmente brilha é na personalização de um componente Aspire existente.
Imagine que você está usando o PostgreSQL, mas precisa de uma extensão específica que não vem na imagem padrão (por exemplo, pg_cron para agendar tarefas no banco de dados).
1. O Dockerfile Personalizado
AppHost/postgres-custom/Dockerfile:
# Começa com a mesma imagem que o Aspire usaria por padrão
FROM postgres:16
# Instala a extensão pg_cron
RUN apt-get update && apt-get install -y postgresql-16-cron
2. A Mágica no AppHost
// Em AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// Começamos com o componente Aspire para PostgreSQL
var postgres = builder.AddPostgres("pg")
// Agora, substituímos a imagem padrão pela nossa construção personalizada
.WithDockerfile("./postgres-custom");
// O ponto crucial: ainda podemos usar todos os métodos de extensão do PostgreSQL!
postgres.WithPgAdmin(); // Isso ainda funciona perfeitamente!
var api = builder.AddProject<Projects.MyApi>("api")
// E a injeção da connection string também funciona como esperado!
.WithReference(postgres);
builder.Build().Run();
Se você tivesse usado builder.AddDockerfile("pg", "./postgres-custom"), você teria um contêiner genérico. Você perderia o método .WithPgAdmin() e teria que construir manualmente a connection string para o seu serviço de API. Ao usar builder.AddPostgres(...).WithDockerfile(...), você obtém o melhor dos dois mundos: uma imagem de contêiner personalizada e toda a integração inteligente fornecida pelo componente Aspire.
Teste seu entendimento!
Você está usando builder.AddRedis("cache") no seu AppHost. Para fins de depuração, você quer usar uma imagem Redis que tenha ferramentas de rede como ping e net-tools pré-instaladas. Você criou o seguinte Dockerfile no diretório AppHost/redis-debug/:
FROM redis:7
RUN apt-get update && apt-get install -y iputils-ping net-tools
Como você modificaria sua chamada no AppHost para usar esta imagem personalizada, garantindo que toda a funcionalidade do componente Aspire.Hosting.Redis (como a injeção de connection string) seja preservada?
Show answer
Você simplesmente encadearia o método WithDockerfile à sua chamada AddRedis existente:
var cache = builder.AddRedis("cache")
.WithDockerfile("./redis-debug");
Isso instrui o Aspire a usar sua imagem personalizada, mantendo o recurso como um RedisResource, preservando todos os seus comportamentos e extensões específicas.
Conclusão
Nesta lição, você aprendeu a ir além das imagens pré-construídas e a integrar serviços definidos por Dockerfiles diretamente no seu modelo de aplicação Aspire. Essa capacidade é essencial para lidar com serviços personalizados, legados ou que requerem configurações específicas não disponíveis nas imagens públicas.
Principais Pontos:
- O Aspire oferece dois métodos para trabalhar com Dockerfiles:
AddDockerfileeWithDockerfile. AddDockerfilecria um novo recurso de contêiner genérico a partir de um Dockerfile.WithDockerfilepersonaliza um recurso de contêiner existente, substituindo sua imagem padrão por uma construída localmente a partir de um Dockerfile.- A principal vantagem do
WithDockerfileé que ele preserva o tipo de recurso original e suas funcionalidades associadas (métodos de extensão, injeção de configuração, etc.), tornando-o a escolha ideal para customizar os componentes oficiais do Aspire.
Com AddContainer e WithDockerfile em seu arsenal, você agora tem a flexibilidade total para orquestrar qualquer tipo de dependência containerizada em seu ambiente de desenvolvimento Aspire.
Próxima lição:
Até agora, dominamos a orquestração de aplicações complexas e com múltiplos serviços na sua máquina local com o .NET Aspire. O próximo passo lógico é pensar em produção. No próximo módulo, mudaremos nosso foco da orquestração de desenvolvimento local para a orquestração de nível de produção com Kubernetes. Você aprenderá os conceitos e objetos fundamentais que fazem do Kubernetes o padrão da indústria para implantar e gerenciar aplicações containerizadas em escala.