Skip to main content

.NET Aspire: Building Cloud-Native Apps Made Simple

February 21, 2026 5 min read

.NET Aspire is the most impactful addition to the .NET ecosystem since minimal APIs. It focuses on the developer inner loop — making local development of distributed apps genuinely pleasant. Let's explore what it does, how to set it up, and why the built-in dashboard alone is worth the migration.

What Aspire Actually Is

Aspire bundles three capabilities: an orchestrator (AppHost wiring services together), a component library (pre-built integrations for Redis, PostgreSQL, RabbitMQ, etc.), and a dashboard (real-time logs, traces, and metrics across all services). It's not a new framework — it's an opinionated layer on top of existing .NET libraries that eliminates the boilerplate of distributed application development.

Setting Up the AppHost

The AppHost project is where you define your distributed application topology. Every service, database, and cache gets registered here, and Aspire handles container management, port assignment, and connection string injection automatically:

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("orders");
var messaging = builder.AddRabbitMQ("messaging");

var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(postgres)
    .WithReference(cache);

var orderApi = builder.AddProject<Projects.OrderApi>("order-api")
    .WithReference(postgres)
    .WithReference(messaging)
    .WithReference(catalogApi);

builder.AddProject<Projects.WebFrontend>("web-frontend")
    .WithExternalHttpEndpoints()
    .WithReference(catalogApi)
    .WithReference(orderApi);

builder.Build().Run();

No Docker Compose. No environment variable juggling. Aspire pulls container images automatically, assigns ports, injects connection strings, and sets up service discovery. The dashboard appears at https://localhost:15888.

To create a new Aspire project from scratch, use the template:

dotnet new aspire-starter --name MyDistributedApp
cd MyDistributedApp
dotnet run --project MyDistributedApp.AppHost

This generates the AppHost, a default service, and the service defaults project — everything wired together and ready to run.

Service Discovery

When you add .WithReference(catalogApi), Aspire configures HTTP client factory so you call services by name:

builder.AddServiceDefaults();
builder.Services.AddHttpClient<CatalogClient>(client =>
{
    client.BaseAddress = new Uri("https+http://catalog-api");
});

public class CatalogClient(HttpClient httpClient)
{
    public async Task<Product?> GetProductAsync(int id)
        => await httpClient.GetFromJsonAsync<Product>($"/api/products/{id}");
}

The https+http:// scheme tries HTTPS first, falls back to HTTP. Aspire's built-in service discovery is designed for the development inner loop — for production AKS deployments, use DNS-based discovery.

Service Defaults and Observability

The AddServiceDefaults() extension method is where Aspire shines for observability. It configures OpenTelemetry traces, metrics, and structured logging across all your services with a single call:

public static IHostApplicationBuilder AddServiceDefaults(
    this IHostApplicationBuilder builder)
{
    builder.ConfigureOpenTelemetry();
    builder.AddDefaultHealthChecks();
    builder.Services.AddServiceDiscovery();
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        http.AddStandardResilienceHandler();
        http.AddServiceDiscovery();
    });
    return builder;
}

Every HTTP request, database query, and message published gets traced automatically. The Aspire dashboard visualizes these traces in real-time — you can see a request flow from the web frontend through the catalog API to the database and back, with timing at each hop.

Integrations and Resilience

Each integration configures health checks, connection pooling, retry policies, and telemetry automatically:

builder.AddServiceDefaults();
builder.AddRedisDistributedCache("cache");
builder.AddNpgsqlDbContext<OrderDbContext>("orders");
builder.AddRabbitMQClient("messaging");

Combine with Microsoft.Extensions.Http.Resilience for production-grade HTTP calls:

builder.Services.AddHttpClient<CatalogClient>(client =>
{
    client.BaseAddress = new Uri("https+http://catalog-api");
})
.AddStandardResilienceHandler(options =>
{
    options.Retry.MaxRetryAttempts = 3;
    options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(10);
    options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);
});

The standard resilience handler adds retry, circuit breaker, and timeout policies using Polly under the hood — configured with sensible defaults that work for most microservice scenarios.

Testing with the Dashboard

The Aspire dashboard at https://localhost:15888 provides immediate visibility into your distributed application. You can see structured logs from all services in one view, distributed traces that show request flow across service boundaries, and metrics like request rates, error rates, and response times. For debugging issues in development, the trace view is invaluable — you can click on a slow request and see exactly which service call took the most time, including database queries and HTTP calls. No need for separate log aggregation tools during development.

Deployment

Aspire orchestrates local development but doesn't deploy your app. Options include:

  • Azure Container Apps via azd init && azd up (first-class support)
  • Kubernetes/AKS via Aspirate tool (aspirate generate) for Helm charts
  • Docker Compose via aspirate generate --output-format compose

The Azure Developer CLI (azd) integration is the smoothest path. It reads the AppHost topology and provisions matching Azure resources — Container Apps for each service, Azure Cache for Redis, Azure Database for PostgreSQL, and so on.

# Deploy your entire Aspire application to Azure
azd init
azd up

# This provisions all infrastructure and deploys all services
# based on the AppHost topology definition

When Not to Use Aspire

Aspire adds value for multi-service applications, but it's not always the right choice. For a single API with a database, the overhead of an AppHost project isn't justified. Similarly, if your team already has mature Docker Compose workflows and monitoring infrastructure, Aspire may not add enough value to justify the migration effort. Evaluate whether the dashboard and service discovery provide benefits your current setup lacks.

Key Takeaways

  • Start with the AppHost and dashboard. The orchestration and observability alone justify Aspire.
  • Don't fight the conventions. Aspire is opinionated about connection strings, health checks, and telemetry — embrace the patterns.
  • Use service discovery for development, DNS for production.
  • Plan your deployment strategy early — decide between ACA, AKS, or Compose before going deep.
  • Add integrations incrementally — start with your core dependencies and expand as needed.

If you're building anything with more than two services in .NET, Aspire should be your starting point.

References

  1. .NET Aspire overview — Official documentation for .NET Aspire concepts and getting started
  2. .NET Aspire GitHub repository — Source code, samples, and issue tracking
  3. .NET Aspire integrations — Available component integrations for Redis, PostgreSQL, RabbitMQ, and more
  4. Deploy .NET Aspire apps to Azure Container Apps — Step-by-step deployment guide using the Azure Developer CLI
  5. .NET Aspire dashboard — Using the built-in observability dashboard for traces, metrics, and logs
Share this post

Comments

Ajit Gangurde

Software Engineer II at Microsoft | 15+ years in .NET & Azure