.NET Aspire 10: Cloud-Native Development from Local to Azure

.NET Aspire 10, released alongside .NET 10, represents Microsoft’s answer to the complexity of cloud-native development. It provides an opinionated, orchestrated approach to building distributed applications with built-in service discovery, health checks, telemetry, and deployment automation. This comprehensive guide explores Aspire’s architecture, the developer experience improvements in version 10, and patterns for deploying Aspire applications to Azure Container Apps and Kubernetes.

What is .NET Aspire?

.NET Aspire is a cloud-ready stack for building observable, production-ready distributed applications. It addresses the pain points of microservices development:

  • Service Discovery: Automatic endpoint resolution between services
  • Configuration: Centralized connection strings and settings management
  • Health Checks: Built-in liveness and readiness probes
  • Telemetry: Unified logging, metrics, and distributed tracing
  • Local Development: Orchestrated containers with a visual dashboard
  • Deployment: One-click deployment to Azure or Kubernetes

.NET Aspire 10 Architecture

graph TB
    subgraph DevMachine ["Developer Machine"]
        AppHost["AppHost Project"]
        Dashboard["Aspire Dashboard"]
        Docker["Docker Containers"]
    end
    
    subgraph AspireApp ["Aspire Application"]
        API["API Service"]
        Worker["Background Worker"]
        Web["Web Frontend"]
    end
    
    subgraph Infrastructure ["Infrastructure Resources"]
        Redis["Redis Cache"]
        Postgres["PostgreSQL"]
        RabbitMQ["RabbitMQ"]
        Blob["Azure Blob Storage"]
    end
    
    subgraph Azure ["Azure Deployment"]
        ACA["Container Apps"]
        ACAE["Container Apps Environment"]
        Monitor["Azure Monitor"]
    end
    
    AppHost --> Dashboard
    AppHost --> Docker
    Docker --> API
    Docker --> Worker
    Docker --> Web
    Docker --> Redis
    Docker --> Postgres
    Docker --> RabbitMQ
    
    API --> Redis
    API --> Postgres
    Worker --> RabbitMQ
    Worker --> Blob
    Web --> API
    
    AppHost -.-> ACA
    ACA --> ACAE
    ACAE --> Monitor
    
    style AppHost fill:#E8F5E9,stroke:#2E7D32
    style Dashboard fill:#E3F2FD,stroke:#1565C0
    style ACA fill:#FFF3E0,stroke:#EF6C00

What’s New in .NET Aspire 10

FeatureAspire 9Aspire 10
DashboardBasic resource viewEnhanced metrics, log streaming, dependency graph
KubernetesManual manifest generationNative Helm chart generation
AI IntegrationBasic OpenAI componentAspire.AI with embeddings, agents, RAG
TestingDistributedApplicationTestingBuilderImproved test isolation, parallel execution
Components35 integrations50+ integrations including Dapr, Temporal
Hot ReloadPartial supportFull hot reload for all project types

Getting Started with Aspire 10

Creating an Aspire Application

# Install/update the Aspire workload
dotnet workload update
dotnet workload install aspire

# Create a new Aspire application
dotnet new aspire-starter -n MyCloudApp
cd MyCloudApp

# Solution structure:
# MyCloudApp/
# ├── MyCloudApp.AppHost/       # Orchestration project
# ├── MyCloudApp.ServiceDefaults/  # Shared configuration
# ├── MyCloudApp.ApiService/    # Backend API
# └── MyCloudApp.Web/           # Blazor frontend

# Run the application
dotnet run --project MyCloudApp.AppHost

Understanding the AppHost

The AppHost project is the heart of Aspire—it defines your distributed application’s topology:

// Program.cs in MyCloudApp.AppHost
var builder = DistributedApplication.CreateBuilder(args);

// Infrastructure resources
var cache = builder.AddRedis("cache")
    .WithDataVolume()  // Persist data across restarts
    .WithRedisInsight();  // Add Redis Insight for debugging

var postgres = builder.AddPostgres("postgres")
    .WithDataVolume()
    .WithPgAdmin()  // Add pgAdmin UI
    .AddDatabase("ordersdb");

var rabbitmq = builder.AddRabbitMQ("messaging")
    .WithManagementPlugin();  // Enable management UI

var storage = builder.AddAzureStorage("storage")
    .RunAsEmulator()  // Use Azurite locally
    .AddBlobs("blobs");

// Application services
var api = builder.AddProject<Projects.MyCloudApp_ApiService>("api")
    .WithReference(cache)
    .WithReference(postgres)
    .WithExternalHttpEndpoints();  // Expose to internet

var worker = builder.AddProject<Projects.MyCloudApp_Worker>("worker")
    .WithReference(rabbitmq)
    .WithReference(storage)
    .WithReplicas(3);  // Run 3 instances

builder.AddProject<Projects.MyCloudApp_Web>("web")
    .WithReference(api)
    .WithExternalHttpEndpoints();

builder.Build().Run();

Service Defaults: Opinionated Best Practices

The ServiceDefaults project provides pre-configured settings for resilience, telemetry, and health checks:

// Extensions.cs in ServiceDefaults
public static IHostApplicationBuilder AddServiceDefaults(
    this IHostApplicationBuilder builder)
{
    // OpenTelemetry for distributed tracing
    builder.ConfigureOpenTelemetry();
    
    // Health checks
    builder.AddDefaultHealthChecks();
    
    // Service discovery
    builder.Services.AddServiceDiscovery();
    
    // Resilient HTTP clients with Polly
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        http.AddStandardResilienceHandler();  // Retry, circuit breaker, timeout
        http.AddServiceDiscovery();
    });
    
    return builder;
}

// In your API service's Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();  // One line adds everything!

builder.AddRedisClient("cache");
builder.AddNpgsqlDbContext<OrdersDbContext>("ordersdb");

var app = builder.Build();
app.MapDefaultEndpoints();  // Health check endpoints
app.MapControllers();
app.Run();
💡
RESILIENCE BUILT-IN

AddStandardResilienceHandler() automatically configures retry policies (exponential backoff), circuit breakers, and timeouts for all HTTP clients. No manual Polly configuration needed!

Aspire Dashboard: Visual Development Experience

The Aspire Dashboard (accessible at https://localhost:17888) provides real-time visibility into your distributed application:

  • Resources: View all services, containers, and their status
  • Console Logs: Aggregated, color-coded logs from all services
  • Structured Logs: Searchable log entries with structured data
  • Traces: Distributed traces showing request flow across services
  • Metrics: Real-time CPU, memory, and custom metrics
  • Dependency Graph: Visual representation of service dependencies (new in Aspire 10)

Aspire 10 AI Integration

Aspire 10 introduces first-class AI support through the Aspire.AI package:

// In AppHost
var openai = builder.AddAzureOpenAI("openai")
    .AddDeployment(new AzureOpenAIDeployment("gpt-5", "gpt-5", "2026-01-01"))
    .AddDeployment(new AzureOpenAIDeployment("embeddings", "text-embedding-3-large", "2024-01-01"));

var vectorDb = builder.AddQdrant("vectors")
    .WithDataVolume();

builder.AddProject<Projects.MyCloudApp_AiService>("ai")
    .WithReference(openai)
    .WithReference(vectorDb);

// In your AI service
public class RagService
{
    private readonly ChatClient _chat;
    private readonly EmbeddingClient _embeddings;
    private readonly QdrantClient _vectors;
    
    public RagService(
        [FromKeyedServices("gpt-5")] ChatClient chat,
        [FromKeyedServices("embeddings")] EmbeddingClient embeddings,
        QdrantClient vectors)
    {
        _chat = chat;
        _embeddings = embeddings;
        _vectors = vectors;
    }
    
    public async Task<string> QueryAsync(string question)
    {
        // Generate embedding for the question
        var embedding = await _embeddings.GenerateEmbeddingAsync(question);
        
        // Search vector database
        var results = await _vectors.SearchAsync("documents", embedding.Vector, limit: 5);
        
        // Generate response with context
        var context = string.Join("
", results.Select(r => r.Payload["content"]));
        var response = await _chat.CompleteChatAsync($"""
            Context: {context}
            
            Question: {question}
            
            Answer based on the context above:
            """);
        
        return response.Value.Content[0].Text;
    }
}

Testing Aspire Applications

public class IntegrationTests : IAsyncLifetime
{
    private DistributedApplication _app = null!;
    private ResourceNotificationService _notifications = null!;
    
    public async Task InitializeAsync()
    {
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.MyCloudApp_AppHost>();
        
        // Override resources for testing
        appHost.Services.ConfigureHttpClientDefaults(http =>
        {
            http.AddStandardResilienceHandler(options =>
            {
                options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
            });
        });
        
        _app = await appHost.BuildAsync();
        _notifications = _app.Services.GetRequiredService<ResourceNotificationService>();
        
        await _app.StartAsync();
        
        // Wait for all resources to be ready
        await _notifications.WaitForResourceHealthyAsync("api");
        await _notifications.WaitForResourceHealthyAsync("postgres");
    }
    
    [Fact]
    public async Task Api_CreateOrder_ReturnsCreated()
    {
        // Get the API endpoint
        var apiUrl = _app.GetEndpoint("api");
        using var client = new HttpClient { BaseAddress = new Uri(apiUrl) };
        
        var order = new { CustomerId = "123", Items = new[] { new { ProductId = "ABC", Quantity = 2 } } };
        var response = await client.PostAsJsonAsync("/orders", order);
        
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
    
    public async Task DisposeAsync()
    {
        await _app.DisposeAsync();
    }
}

Deploying to Azure Container Apps

Aspire 10 provides seamless deployment to Azure Container Apps with a single command:

# Login to Azure
az login
azd auth login

# Initialize Azure Developer CLI
azd init

# Deploy to Azure Container Apps
azd up

# What happens behind the scenes:
# 1. Builds container images for all projects
# 2. Pushes to Azure Container Registry
# 3. Creates Container Apps Environment
# 4. Deploys all services with proper configuration
# 5. Sets up managed identities and connections
# 6. Configures Azure Monitor for observability

Generated Bicep/ARM

The azd infra synth command generates Bicep templates you can customize:

// Generated infra/main.bicep
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = {
  name: 'cae-${resourceToken}'
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'azure-monitor'
    }
    workloadProfiles: [
      { name: 'Consumption', workloadProfileType: 'Consumption' }
    ]
  }
}

resource apiContainerApp 'Microsoft.App/containerApps@2024-03-01' = {
  name: 'api'
  location: location
  properties: {
    environmentId: containerAppsEnvironment.id
    configuration: {
      ingress: {
        external: true
        targetPort: 8080
      }
      secrets: [
        { name: 'connection-string', value: postgresConnectionString }
      ]
    }
    template: {
      containers: [
        {
          name: 'api'
          image: '${containerRegistry.properties.loginServer}/api:${imageTag}'
          resources: { cpu: json('0.5'), memory: '1Gi' }
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 10
        rules: [
          { name: 'http', http: { metadata: { concurrentRequests: '100' } } }
        ]
      }
    }
  }
}

Deploying to Kubernetes with Helm

Aspire 10 introduces native Helm chart generation:

# Generate Helm charts
dotnet run --project MyCloudApp.AppHost -- --publisher helm --output-path ./charts

# Deploy to Kubernetes
helm install myapp ./charts/myapp   --namespace myapp   --create-namespace   --set image.tag=v1.0.0   --set postgresql.auth.password=$PG_PASSWORD
ℹ️
KUBERNETES NOTE

Helm charts include Kubernetes-native service discovery (DNS), ConfigMaps for configuration, and Secrets for sensitive data. The Aspire service discovery seamlessly switches to Kubernetes DNS in production.

Available Aspire Components

CategoryComponents
DatabasesPostgreSQL, SQL Server, MySQL, MongoDB, CosmosDB, Oracle
CachingRedis, Valkey, Garnet, Memcached
MessagingRabbitMQ, Kafka, Azure Service Bus, AWS SQS
StorageAzure Blob, AWS S3, MinIO
SearchElasticsearch, Azure AI Search, Meilisearch
AI/MLAzure OpenAI, Ollama, Qdrant, Milvus
OrchestrationDapr, Temporal, Conductor
ObservabilitySeq, Grafana, Jaeger, Zipkin

Key Takeaways

  • .NET Aspire 10 provides an opinionated framework for building cloud-native distributed applications with built-in observability and resilience.
  • The AppHost project defines your entire distributed system topology, including services, databases, and infrastructure.
  • ServiceDefaults provides one-line configuration for health checks, telemetry, service discovery, and resilient HTTP clients.
  • The Aspire Dashboard offers real-time visibility into logs, traces, metrics, and dependencies during development.
  • Deployment to Azure Container Apps or Kubernetes is automated with azd up or Helm chart generation.

Conclusion

.NET Aspire 10 significantly reduces the complexity of building and deploying distributed applications. By providing opinionated defaults for the hard parts—service discovery, health checks, observability, and deployment—it lets developers focus on business logic rather than infrastructure plumbing. For teams building microservices on .NET, Aspire is now the recommended starting point, offering a smooth path from local development to production in Azure or Kubernetes.

References


Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.