If you've ever juggled Docker Compose files, environment variables, and connection strings just to run your .NET app locally, Aspire is the answer you didn't know you were waiting for. It gives you a single dashboard to orchestrate, monitor, and debug all your services β€” databases, caches, message brokers, and your own microservices β€” from one place.

This guide walks through Aspire from zero to a fully orchestrated local development environment with live telemetry.

What Problem Does Aspire Solve?

Modern .NET applications are rarely a single project. A typical system looks like:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ TYPICAL .NET MICROSERVICE APP β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Web API β”‚ β”‚ Worker β”‚ β”‚ Blazor Frontendβ”‚ β”‚ β”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Shared Infrastructure β”‚ β”‚ β”‚ β”‚ PostgreSQL Β· Redis Β· RabbitMQ Β· Azure β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ WITHOUT ASPIRE: β”‚ β”‚ - 200-line docker-compose.yml β”‚ β”‚ - .env files for connection strings β”‚ β”‚ - Manual health checking β”‚ β”‚ - No unified logging β”‚ β”‚ - "Works on my machine" syndrome β”‚ β”‚ β”‚ β”‚ WITH ASPIRE: β”‚ β”‚ - 15 lines of C# in AppHost β”‚ β”‚ - Auto-injected connection strings β”‚ β”‚ - Live dashboard with logs + traces β”‚ β”‚ - One F5 to start everything β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Setting Up Your First Aspire App

Step 1: Create the AppHost

The AppHost is Aspire's orchestrator. It defines what services your app needs and how they connect.

dotnet new aspire-starter -n MyApp
cd MyApp

This creates four projects:

MyApp/
β”œβ”€β”€ MyApp.AppHost/          # The orchestrator
β”œβ”€β”€ MyApp.ServiceDefaults/  # Shared config (telemetry, health checks)
β”œβ”€β”€ MyApp.ApiService/       # Your Web API
└── MyApp.Web/              # Blazor frontend

Step 2: Define Your Resources

The Program.cs in the AppHost is where the magic happens:

var builder = DistributedApplication.CreateBuilder(args);

// Infrastructure
var postgres = builder.AddPostgres("postgres")
    .WithPgAdmin();

var redis = builder.AddRedis("cache");

var rabbitmq = builder.AddRabbitMQ("messaging")
    .WithManagementPlugin();

// Application database
var ordersDb = postgres.AddDatabase("orders");

// Services
var apiService = builder.AddProject<Projects.MyApp_ApiService>("api")
    .WithReference(ordersDb)
    .WithReference(redis)
    .WithReference(rabbitmq);

var workerService = builder.AddProject<Projects.MyApp_Worker>("worker")
    .WithReference(ordersDb)
    .WithReference(rabbitmq);

builder.AddProject<Projects.MyApp_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
    .WithReference(redis);

builder.Build().Run();

That's it. 15 lines of C# replaces hundreds of lines of Docker Compose YAML, environment variable files, and startup scripts.

Step 3: Use the Resources in Your Services

In your API service, Aspire automatically injects connection strings:

// In MyApp.ApiService/Program.cs
var builder = WebApplication.CreateBuilder(args);

// These extension methods come from Aspire integrations
builder.AddNpgsqlDbContext<OrdersContext>("orders");
builder.AddRedisClient("cache");
builder.AddRabbitMQClient("messaging");

// Standard service configuration
builder.AddServiceDefaults();

var app = builder.Build();
app.MapDefaultEndpoints();

app.MapGet("/orders", async (OrdersContext db) =>
    await db.Orders.ToListAsync());

app.Run();

No connection strings in appsettings.json. No environment variables to manage. Aspire handles all service discovery and connection configuration automatically.

The Dashboard: Your New Best Friend

Press F5 (or dotnet run --project MyApp.AppHost), and the Aspire dashboard opens in your browser.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ASPIRE DASHBOARD β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ RESOURCES STATE β”‚ β”‚ ───────── ───── β”‚ β”‚ 🟒 postgres (Container) Running β”‚ β”‚ 🟒 pgadmin (Container) Running β”‚ β”‚ 🟒 cache (Container) Running β”‚ β”‚ 🟒 messaging (Container) Running β”‚ β”‚ 🟒 api (Project) Running β”‚ β”‚ 🟒 worker (Project) Running β”‚ β”‚ 🟒 webfrontend (Project) Running β”‚ β”‚ β”‚ β”‚ TABS: Resources β”‚ Console β”‚ Structured Logs β”‚ β”‚ β”‚ Traces β”‚ Metrics β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ STRUCTURED LOGS (live stream) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 10:42:01 [api] GET /orders 200 12ms β”‚ β”‚ β”‚ β”‚ 10:42:01 [api] Cache HIT orders:list β”‚ β”‚ β”‚ β”‚ 10:42:02 [worker] Processing order #4521 β”‚ β”‚ β”‚ β”‚ 10:42:02 [worker] Published OrderCompleted β”‚ β”‚ β”‚ β”‚ 10:42:03 [webfrontend] SignalR connected β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What the Dashboard Shows

  1. Resources tab β€” all containers and projects with status, endpoints, and environment variables
  2. Console logs β€” stdout/stderr from every service, filterable and searchable
  3. Structured logs β€” OpenTelemetry-powered structured logging across all services
  4. Traces β€” distributed traces showing request flow across services
  5. Metrics β€” real-time metrics for every service (request rate, error rate, latency)

The traces view is particularly powerful. Click on any trace to see the full request path:

DISTRIBUTED TRACE: GET /orders

webfrontend ──────────────────────────────────────► 245ms total β”‚ β”œβ”€ HTTP GET api/orders ──────────────────────► 42ms β”‚ β”‚ β”‚ β”œβ”€ Redis GET orders:list ───► 1ms (MISS) β”‚ β”‚ β”‚ β”œβ”€ PostgreSQL SELECT ────────► 18ms β”‚ β”‚ query: SELECT * FROM orders WHERE... β”‚ β”‚ β”‚ └─ Redis SET orders:list ───► 2ms β”‚ └─ Render Blazor component ─────────────────► 12ms

Adding Integrations

Aspire has integrations (NuGet packages) for dozens of services:

Database Integrations

// AppHost
var sql = builder.AddSqlServer("sql").AddDatabase("catalog");
var mongo = builder.AddMongoDB("mongo").AddDatabase("analytics");
var cosmos = builder.AddAzureCosmosDB("cosmos").AddDatabase("events");

// Service
builder.AddSqlServerDbContext<CatalogContext>("catalog");
builder.AddMongoDBClient("analytics");

Cloud Service Integrations

// Azure
var storage = builder.AddAzureStorage("storage");
var blobs = storage.AddBlobs("blobs");
var queues = storage.AddQueues("queues");
var servicebus = builder.AddAzureServiceBus("sb");

// AWS (community integration)
var sqs = builder.AddAWSSQSQueue("orders-queue");

Observability Integrations

The ServiceDefaults project configures OpenTelemetry automatically:

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

This gives every service:

  • Distributed tracing with automatic span propagation
  • Structured logging with correlation IDs
  • Health check endpoints (/health, /alive)
  • HTTP client resilience (retries, circuit breakers, timeouts)
  • Service discovery (no hardcoded URLs)

Custom Resources

Need something Aspire doesn't have a built-in integration for? Create a custom resource:

// Add any container as a resource
var jaeger = builder.AddContainer("jaeger", "jaegertracing/all-in-one")
    .WithHttpEndpoint(port: 16686, targetPort: 16686, name: "ui")
    .WithEndpoint(port: 4317, targetPort: 4317, name: "otlp");

// Add an executable
var mockServer = builder.AddExecutable("mock-api", "node",
    workingDirectory: "../mock-server",
    "index.js")
    .WithHttpEndpoint(port: 3001);

Deployment: From Local to Cloud

Aspire isn't just for local development. The same AppHost model deploys to Azure:

# Install the Azure Developer CLI
az extension add --name containerapp

# Deploy to Azure Container Apps
azd init
azd up

Aspire generates the infrastructure automatically:

  • Each project becomes a Container App
  • PostgreSQL becomes Azure Database for PostgreSQL
  • Redis becomes Azure Cache for Redis
  • RabbitMQ becomes Azure Service Bus
  • Connection strings are injected via managed identity
LOCAL (Aspire) AZURE (azd up) ────────────── ────────────── postgres container ───► Azure Database for PostgreSQL redis container ───► Azure Cache for Redis rabbitmq container ───► Azure Service Bus api project ───► Container App (api) worker project ───► Container App (worker) webfrontend project ───► Container App (webfrontend)

Same code. Same AppHost. Different environment.

Testing with Aspire

Aspire includes a testing framework for integration tests:

[Fact]
public async Task GetOrders_ReturnsOk()
{
    // Arrange - spin up the full distributed app
    var appHost = await DistributedApplicationTestingBuilder
        .CreateAsync<Projects.MyApp_AppHost>();

    await using var app = await appHost.BuildAsync();
    await app.StartAsync();

    // Act
    var httpClient = app.CreateHttpClient("api");
    var response = await httpClient.GetAsync("/orders");

    // Assert
    response.EnsureSuccessStatusCode();
    var orders = await response.Content
        .ReadFromJsonAsync<List<Order>>();
    Assert.NotNull(orders);
}

This spins up the entire distributed application β€” all containers, all projects β€” runs your test, and tears everything down. Integration tests that actually test integration.

Common Patterns

Health-Check-Driven Startup

Aspire can wait for dependencies before starting services:

var apiService = builder.AddProject<Projects.MyApp_ApiService>("api")
    .WithReference(ordersDb)
    .WaitFor(ordersDb)     // Don't start API until DB is healthy
    .WaitFor(redis);       // Don't start API until cache is ready

Environment-Specific Configuration

if (builder.Environment.IsDevelopment())
{
    var postgres = builder.AddPostgres("postgres")
        .WithPgAdmin();  // Only add PgAdmin in dev
}
else
{
    var postgres = builder.AddAzurePostgresFlexibleServer("postgres");
}

Shared Projects Across Services

var sharedLib = builder.AddProject<Projects.MyApp_Shared>("shared");

var api = builder.AddProject<Projects.MyApp_ApiService>("api")
    .WithReference(sharedLib);

var worker = builder.AddProject<Projects.MyApp_Worker>("worker")
    .WithReference(sharedLib);

Aspire vs. Docker Compose

Feature Docker Compose Aspire
Language YAML C#
Service discovery Manual DNS/env vars Automatic
Connection strings Manual env vars Auto-injected
Health checks Manual configuration Built-in
Observability Add Jaeger/Prometheus manually Built-in dashboard
Debugging Attach debugger manually F5 debugging
Cloud deployment Write Terraform/Bicep separately azd up
IDE integration None Full IntelliSense

When NOT to Use Aspire

Aspire is excellent for .NET-centric applications, but it's not for everything:

  • Polyglot teams β€” if your services are Python, Go, and Java, Aspire adds minimal value
  • Existing Docker Compose setups β€” if your compose file works and your team knows it, migration has a cost
  • Non-Azure deployments β€” while Aspire works locally for any stack, cloud deployment is Azure-first
  • Simple single-project apps β€” Aspire adds orchestration overhead that a single API doesn't need

Getting Started Today

# Install the Aspire workload
dotnet workload install aspire

# Create a new Aspire app
dotnet new aspire-starter -n MyApp

# Run it
cd MyApp
dotnet run --project MyApp.AppHost

The dashboard opens automatically. You'll see your services starting, logs streaming, and traces appearing β€” all in under a minute.

Aspire is the developer experience .NET always deserved. It takes the pain out of distributed development and replaces it with a dashboard that makes you feel like you're actually in control.

Published by the TechAI Explained Team.