
Aspire
- 109 installs
- 70 repo stars
- Updated July 26, 2026
- rysweet/amplihack
Scaffold and wire .NET Aspire distributed apps—services, containers, observability, and local dev orchestration—within amplihack-guided agent workflows.
About
aspire in rysweet/amplihack guides agents through Microsoft .NET Aspire: app host projects, service wiring, containers, and observability hooks for cloud-native backends. It bridges amplihack automation with Aspire’s opinionated local-to-cloud development loop for APIs and SaaS services.
- .NET Aspire app host setup
- Service discovery and references
- Containerized dev orchestration
- OpenTelemetry defaults
- Cloud-ready service templates
Aspire by the numbers
- 109 all-time installs (skills.sh)
- +1 installs in the week ending Jul 26, 2026 (Skillselion tracking)
- Ranked #78 of 153 .NET & C# skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rysweet/amplihack --skill aspireAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 109 |
|---|---|
| repo stars | ★ 70 |
| Last updated | July 26, 2026 |
| Repository | rysweet/amplihack ↗ |
What it does
Scaffold and wire .NET Aspire distributed apps—services, containers, observability, and local dev orchestration—within amplihack-guided agent workflows.
Files
Aspire Orchestration
Overview
Code-first orchestration for polyglot distributed apps. AppHost defines topology, aspire run orchestrates locally, azd deploy deploys to Azure.
Auto-activates on keywords: aspire, microservices, distributed app, service discovery, orchestration
Quick Start
# Install .NET 8+ and Aspire workload
# See: https://learn.microsoft.com/dotnet/aspire/fundamentals/setup-tooling
dotnet workload update
dotnet workload install aspire
# Create AppHost (orchestrates services in ANY language)
dotnet new aspire-apphost -n MyApp
# Basic AppHost - orchestrate Python, Node.js, .NET services
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("cache");
// Python service
var pythonApi = builder.AddExecutable("python-api", "python", ".").WithArgs("app.py").WithReference(redis);
// Node.js service
var nodeApi = builder.AddExecutable("node-api", "node", ".").WithArgs("server.js").WithReference(redis);
// .NET service
var dotnetApi = builder.AddProject<Projects.Api>("api").WithReference(redis);
builder.Build().Run();
# Run (orchestrates ALL languages)
aspire run # Dashboard opens at http://localhost:15888Core Workflows
Project Setup
dotnet new aspire-apphost -n MyApp
dotnet new webapi -n MyApp.Api
dotnet add MyApp.AppHost reference MyApp.ApiAppHost: Resource topology in Program.cs ServiceDefaults: Shared config (logging, telemetry, resilience) Services: Your apps (APIs, workers, web apps)
Dependency Configuration
// PostgreSQL
var postgres = builder.AddPostgres("db").AddDatabase("mydb");
var api = builder.AddProject<Projects.Api>("api").WithReference(postgres);
// Redis
var redis = builder.AddRedis("cache").WithRedisCommander();
var api = builder.AddProject<Projects.Api>("api").WithReference(redis);
// RabbitMQ
var rabbitmq = builder.AddRabbitMQ("messaging");
var worker = builder.AddProject<Projects.Worker>("worker").WithReference(rabbitmq);
// Access in code (connection strings auto-injected)
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("cache");
});Local Development
aspire run # Starts all servicesDashboard (localhost:15888): Resources, logs, traces, metrics Hot Reload: Auto-rebuild on code changes Debugging: Attach to individual services via IDE
Cloud Deployment
azd init # Initialize Azure Developer CLI
azd up # Deploy (generates Bicep → Azure Container Apps)
azd deploy -e production # Deploy to specific environmentGenerates: Bicep → Container Apps + networking + managed identities
Navigation Guide
When setting up projects:
- examples.md lines 8-31 → Minimal project
- examples.md lines 518-608 → Add Python service
- examples.md lines 610-669 → Add Node.js service
- examples.md lines 671-768 → Add Go service
When adding infrastructure:
- reference.md lines 47-148 → Database APIs (PostgreSQL, Redis, MongoDB)
- examples.md lines 39-95 → Redis integration
- examples.md lines 102-176 → PostgreSQL integration
When deploying:
- commands.md lines 215-288 → Full azd workflow
- examples.md lines 387-515 → Azure deployment walkthrough
- patterns.md lines 5-42 → HA configuration
When debugging:
- troubleshooting.md lines 5-112 → Orchestration failures
- troubleshooting.md lines 291-397 → Connection issues
- commands.md lines 131-179 → Debug commands
Quick Reference
Essential commands: See commands.md for complete reference
Polyglot patterns:
builder.AddProject<Projects.Api>("api"); // .NET
builder.AddExecutable("python-api", "python", ".").WithArgs("app.py"); // Python
builder.AddExecutable("node-api", "node", ".").WithArgs("server.js"); // Node.js
builder.AddExecutable("go-svc", "go", ".").WithArgs("run", "main.go"); // GoService discovery: .WithReference(redis) in AppHost → GetConnectionString("cache") in service
Integration with Amplihack
Command: /ultrathink "Setup Aspire for microservices"
- prompt-writer clarifies requirements → architect uses reference.md for API design
- builder uses examples.md for implementation → reviewer checks patterns.md for best practices
- tester uses troubleshooting.md for validation
Agent-Skill mapping:
- architect → reference.md (API design)
- builder → examples.md (implementation)
- reviewer → patterns.md (best practices)
- tester → troubleshooting.md (validation)
- all agents → commands.md (CLI operations)
Aspire Command Reference
Complete command reference for .NET Aspire CLI, Azure Developer CLI (azd), and related tooling.
Installation Commands
Aspire Installation
macOS/Linux:
# Install via script
curl -sSL https://aspire.dev/install.sh | bash
# Verify installation
aspire --version
# Update to latest version
aspire upgradeWindows (PowerShell):
# Install via script
irm https://aspire.dev/install.ps1 | iex
# Verify installation
aspire --version
# Update to latest version
aspire upgradeAlternative (via .NET SDK):
# Install as .NET global tool (all platforms)
dotnet tool install -g Microsoft.DotNet.Aspire
# Update global tool
dotnet tool update -g Microsoft.DotNet.AspireAzure Developer CLI (azd)
macOS/Linux:
# Install via homebrew (macOS)
brew tap azure/azd && brew install azd
# Or via script (macOS/Linux)
curl -fsSL https://aka.ms/install-azd.sh | bash
# Verify installation
azd versionWindows (PowerShell):
# Install via PowerShell
powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"
# Or via winget
winget install microsoft.azd
# Verify installation
azd versionPrerequisites
.NET SDK (Required):
# Install .NET 9 SDK
# Windows: Download from https://dot.net
# macOS: brew install dotnet-sdk
# Linux: See https://learn.microsoft.com/dotnet/core/install/linux
# Verify installation
dotnet --version # Should be 9.0.0 or higherDocker Desktop (Required for local development):
# Verify Docker is running
docker --version
docker ps # Should not errorProject Creation Commands
Create AppHost
# Create AppHost project (orchestrates all services)
dotnet new aspire-apphost -n MyApp
# Options:
dotnet new aspire-apphost -n MyApp --output ./src/MyApp.AppHostCreate Service Projects
# Create .NET API project
dotnet new webapi -n MyApp.Api
# Create .NET Web project (Blazor)
dotnet new blazor -n MyApp.Web
# Create .NET Worker project (background service)
dotnet new worker -n MyApp.Worker
# Add ServiceDefaults (shared config)
dotnet new aspire-servicedefaults -n MyApp.ServiceDefaultsLink Projects
# Add project references to AppHost
cd MyApp.AppHost
dotnet add reference ../MyApp.Api/MyApp.Api.csproj
dotnet add reference ../MyApp.Web/MyApp.Web.csproj
# Add ServiceDefaults reference to services
cd ../MyApp.Api
dotnet add reference ../MyApp.ServiceDefaults/MyApp.ServiceDefaults.csprojComplete Project Setup
# One-liner: Create full Aspire solution
dotnet new aspire -n MyApp --use-redis-cache
# Creates:
# - MyApp.AppHost (orchestration)
# - MyApp.ServiceDefaults (shared config)
# - MyApp.Api (sample API)
# - MyApp.Web (sample Blazor app)
# - Solution file linking everythingLocal Development Commands
Running Services
Basic Run:
# Start all services defined in AppHost
cd MyApp.AppHost
aspire run
# Or using dotnet
dotnet run
# Dashboard opens automatically at http://localhost:15888Run with Options:
# Run without opening browser
aspire run --no-launch-profile
# Run with specific launch profile
dotnet run --launch-profile https
# Run with environment variables
ASPNETCORE_ENVIRONMENT=Staging aspire run
# Run with custom dashboard port
aspire run --dashboard-port 18888Stopping Services
# Stop all services (Ctrl+C in terminal)
# Or:
aspire stop
# Force stop all containers
docker stop $(docker ps -q --filter "label=aspire")
# Clean up volumes (removes all data)
aspire down --volumesService Management
View Running Services:
# List all Aspire resources
aspire ps
# View Docker containers
docker ps --filter "label=aspire"
# View resource status
aspire statusRestart Services:
# Restart specific service (via Dashboard)
# Or restart all:
aspire restart
# Restart specific container
docker restart <container-name>Debugging Commands
Logs
View Logs via Dashboard:
# Dashboard logs tab shows all services
# Access at http://localhost:15888/logsView Logs via CLI:
# Follow logs for specific service
aspire logs api --follow
# View last 100 lines
aspire logs api --tail 100
# View logs for all services
aspire logs --all
# Docker logs (alternative)
docker logs -f <container-name>Distributed Tracing
Access Traces:
# Dashboard traces tab
# Access at http://localhost:15888/traces
# Export traces (OpenTelemetry format)
aspire traces export --output ./traces.jsonMetrics
View Metrics:
# Dashboard metrics tab
# Access at http://localhost:15888/metrics
# Prometheus endpoint
curl http://localhost:15888/metrics/prometheusDashboard Commands
# Open Dashboard in browser
aspire dashboard
# Dashboard with custom port
aspire dashboard --port 18888
# Dashboard with authentication
aspire dashboard --auth basic --username admin --password secretConfiguration Commands
User Secrets
# Initialize user secrets for AppHost
cd MyApp.AppHost
dotnet user-secrets init
# Set secret
dotnet user-secrets set "ApiKeys:External" "secret-key-12345"
# List secrets
dotnet user-secrets list
# Remove secret
dotnet user-secrets remove "ApiKeys:External"
# Clear all secrets
dotnet user-secrets clearEnvironment Configuration
# Set environment for run
export ASPNETCORE_ENVIRONMENT=Staging
aspire run
# Windows PowerShell:
$env:ASPNETCORE_ENVIRONMENT="Staging"
aspire runAzure Deployment Commands
Initialize Azure Deployment
# Initialize azd for Azure deployment
cd MyApp.AppHost
azd init
# Interactive prompts:
# - Environment name (e.g., "production")
# - Azure subscription
# - Azure regionLogin to Azure
# Login to Azure
azd auth login
# Login with specific tenant
azd auth login --tenant-id <tenant-id>
# Verify login status
azd auth statusDeploy to Azure
Full Deployment:
# Deploy everything (infrastructure + code)
azd up
# This runs:
# 1. azd provision (creates Azure resources)
# 2. azd deploy (deploys code)Incremental Deployment:
# Deploy code only (no infrastructure changes)
azd deploy
# Deploy specific service
azd deploy api
# Provision infrastructure only (no code deployment)
azd provisionEnvironment-Specific Deployment:
# Deploy to specific environment
azd deploy -e production
# Create new environment
azd env new staging
azd env select staging
azd upMonitor Deployment
# View deployment logs
azd deploy --output json
# Show deployed resources
azd show
# Get service endpoint URLs
azd endpointsTear Down Azure Resources
# Delete all Azure resources
azd down
# Delete without confirmation prompt
azd down --force --purge
# List resources before deleting
azd showTesting Commands
Run Tests
# Run all tests
dotnet test
# Run tests with coverage
dotnet test /p:CollectCoverage=true
# Run specific test project
dotnet test MyApp.Api.Tests/MyApp.Api.Tests.csprojIntegration Tests
# Run integration tests against local Aspire
aspire run &
dotnet test --filter Category=IntegrationBuild Commands
Build Projects
# Build all projects
dotnet build
# Build specific project
dotnet build MyApp.Api/MyApp.Api.csproj
# Build for release
dotnet build -c ReleasePublish Projects
# Publish for deployment
dotnet publish -c Release
# Publish to folder
dotnet publish -c Release -o ./publish
# Publish for Linux container
dotnet publish -c Release -r linux-x64NuGet Package Management
Add Aspire Components
# Add Redis component
dotnet add package Aspire.Hosting.Redis
# Add PostgreSQL component
dotnet add package Aspire.Hosting.PostgreSQL
# Add Azure Key Vault integration
dotnet add package Aspire.Azure.KeyVault
# Search for Aspire packages
dotnet search Aspire.HostingUpdate Packages
# Update all Aspire packages to latest
dotnet list package --outdated
dotnet add package Aspire.Hosting --version 9.0.0
# Update all packages
dotnet restore --forceDiagnostics Commands
Health Checks
# Check service health via Dashboard
# Access at http://localhost:15888/health
# Or via curl
curl http://localhost:5000/health
curl http://localhost:5000/health/readyTroubleshooting
# View DCP logs (orchestrator)
aspire logs dcp
# View all container logs
docker logs $(docker ps -q --filter "label=aspire")
# Check for port conflicts
netstat -an | grep 15888 # Dashboard port
netstat -an | grep 6379 # Redis
netstat -an | grep 5432 # PostgreSQLClean Up
# Remove all Aspire containers
docker rm -f $(docker ps -aq --filter "label=aspire")
# Remove all Aspire volumes
docker volume rm $(docker volume ls -q --filter "label=aspire")
# Clean Docker system
docker system prune -a --volumesAdvanced Commands
Custom Dashboard Configuration
# Run Dashboard with custom OTLP endpoint
aspire dashboard --otlp-endpoint http://localhost:4317
# Dashboard with resource limits
aspire dashboard --max-logs 100000 --max-traces 50000Export Configuration
# Export AppHost manifest (for debugging)
aspire manifest --output ./manifest.json
# Generate Docker Compose (for testing)
aspire export docker-compose --output ./docker-compose.ymlOffline Development
# Pull all required Docker images
docker pull redis:7.2-alpine
docker pull postgres:15
docker pull rabbitmq:3-management
# Run without internet (uses cached images)
aspire run --offlineQuick Reference Table
| Task | Command | Notes |
|---|---|---|
| Setup | ||
| Install Aspire | `curl -sSL https://aspire.dev/install.sh \ | bash` |
| Install Aspire | `irm https://aspire.dev/install.ps1 \ | iex` |
| Install azd | brew install azd | macOS |
| Install azd | winget install microsoft.azd | Windows |
| Development | ||
| Create project | dotnet new aspire -n MyApp | Full template |
| Run locally | aspire run | Opens Dashboard |
| View logs | aspire logs api --follow | Follow logs |
| Stop services | aspire stop | Stop all |
| Deployment | ||
| Initialize Azure | azd init | One-time setup |
| Login to Azure | azd auth login | Required once |
| Deploy everything | azd up | Infrastructure + code |
| Deploy code only | azd deploy | Faster updates |
| Tear down | azd down | Delete resources |
| Debugging | ||
| Dashboard | http://localhost:15888 | All observability |
| Health check | curl localhost:5000/health | Service health |
| View traces | Dashboard → Traces tab | Distributed tracing |
| View metrics | Dashboard → Metrics tab | Performance data |
| Cleanup | ||
| Remove containers | docker rm -f $(docker ps -aq) | All containers |
| Remove volumes | aspire down --volumes | Includes data |
| Clean system | docker system prune -a | Full cleanup |
Common Command Combinations
Fresh Start:
aspire down --volumes
docker system prune -f
aspire runDeploy to Azure Production:
azd auth login
azd env select production
azd deploy
azd endpoints # Get URLsDebug Failing Service:
aspire logs api --tail 100
docker logs <container-id>
curl http://localhost:5000/healthUpdate and Redeploy:
# Local:
aspire restart
# Azure:
azd deploy -e productionPlatform-Specific Notes
Windows-Specific
# Use PowerShell for scripts
$env:ASPNETCORE_ENVIRONMENT="Production"
# Docker Desktop must be running
# Check: Get-Process "Docker Desktop"macOS-Specific
# Use Homebrew for installation
brew install azd dotnet-sdk
# Docker Desktop must be running
# Check: docker psLinux-Specific
# Install .NET SDK first
# See: https://learn.microsoft.com/dotnet/core/install/linux
# Use script installation for Aspire
curl -sSL https://aspire.dev/install.sh | bash
# Add to PATH if needed
export PATH="$PATH:$HOME/.aspire/bin"Error Resolution Commands
Port Already in Use:
# Find process using port
lsof -i :15888 # macOS/Linux
netstat -ano | findstr :15888 # Windows
# Kill process
kill -9 <PID> # macOS/Linux
taskkill /PID <PID> /F # WindowsDocker Not Running:
# Start Docker Desktop manually
# Or check service:
sudo systemctl start docker # LinuxCache Issues:
# Clear NuGet cache
dotnet nuget locals all --clear
# Clear Docker build cache
docker builder prune -aUse this reference for all Aspire CLI operations and deployment workflows.
Aspire Working Examples
Copy-paste examples for common Aspire scenarios. All examples tested with .NET 8 and Aspire 9.0+.
See also: Official samples for production-ready applications.
Basic Project Setup
Minimal Aspire Application
Create Project:
dotnet new aspire-apphost -n MinimalApp
cd MinimalApp
dotnet new webapi -n MinimalApp.Api
dotnet add MinimalApp.AppHost reference MinimalApp.ApiAppHost (MinimalApp.AppHost/Program.cs):
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject<Projects.MinimalApp_Api>("api");
builder.Build().Run();Run:
aspire run
# Dashboard opens at http://localhost:15888
# API available at http://localhost:5000Expected Output:
- Dashboard shows "api" resource with "Healthy" status
- Console logs from API visible in Dashboard
- OpenTelemetry traces for HTTP requests
Redis Integration
API with Redis Cache
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("cache")
.WithDataVolume() // Persist data across runs
.WithRedisCommander(); // Add Redis Commander UI
var api = builder.AddProject<Projects.CacheApi>("api")
.WithReference(redis);
builder.Build().Run();API Configuration (Program.cs):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("cache");
});
var app = builder.Build();
app.MapGet("/cache/{key}", async (string key, IDistributedCache cache) =>
{
var value = await cache.GetStringAsync(key);
return value ?? "Not found";
});
app.MapPost("/cache/{key}", async (string key, string value, IDistributedCache cache) =>
{
await cache.SetStringAsync(key, value);
return Results.Ok();
});
app.Run();Test:
aspire run
# Set cache value
curl -X POST http://localhost:5000/cache/test -d "Hello Aspire"
# Get cache value
curl http://localhost:5000/cache/test
# Returns: "Hello Aspire"
# View in Redis Commander: http://localhost:8081Connection String Generated:
Local: localhost:6379
Azure: my-app-cache.redis.cache.windows.net:6380,ssl=True,password=...PostgreSQL Integration
API with PostgreSQL Database
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("pg")
.WithDataVolume()
.WithPgAdmin()
.AddDatabase("appdb");
var api = builder.AddProject<Projects.DataApi>("api")
.WithReference(postgres);
builder.Build().Run();API - Entity Framework Configuration:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("appdb")));
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.EnsureCreatedAsync();
}
app.MapGet("/users", async (AppDbContext db) =>
await db.Users.ToListAsync());
app.MapPost("/users", async (User user, AppDbContext db) =>
{
db.Users.Add(user);
await db.SaveChangesAsync();
return Results.Created($"/users/{user.Id}", user);
});
app.Run();DbContext:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
}
public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}Test:
aspire run
curl -X POST http://localhost:5000/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
curl http://localhost:5000/users
# View in pgAdmin: http://localhost:5050 (admin@admin.com / admin)Multi-Service Application
Complete E-Commerce Example
Project Structure:
dotnet new aspire-apphost -n ECommerce
cd ECommerce
dotnet new webapi -n ECommerce.CatalogApi
dotnet new webapi -n ECommerce.OrderApi
dotnet new blazor -n ECommerce.Web
dotnet new worker -n ECommerce.OrderProcessor
dotnet add ECommerce.AppHost reference ECommerce.CatalogApi
dotnet add ECommerce.AppHost reference ECommerce.OrderApi
dotnet add ECommerce.AppHost reference ECommerce.Web
dotnet add ECommerce.AppHost reference ECommerce.OrderProcessorAppHost:
var builder = DistributedApplication.CreateBuilder(args);
// Databases
var catalogDb = builder.AddPostgres("pg-catalog")
.WithDataVolume()
.AddDatabase("catalogdb");
var orderDb = builder.AddPostgres("pg-order")
.WithDataVolume()
.AddDatabase("orderdb");
// Cache
var redis = builder.AddRedis("cache")
.WithDataVolume();
// Messaging
var rabbitmq = builder.AddRabbitMQ("messaging")
.WithDataVolume()
.WithManagementPlugin();
// Backend APIs
var catalogApi = builder.AddProject<Projects.ECommerce_CatalogApi>("catalog-api")
.WithReference(catalogDb)
.WithReference(redis);
var orderApi = builder.AddProject<Projects.ECommerce_OrderApi>("order-api")
.WithReference(orderDb)
.WithReference(redis)
.WithReference(rabbitmq);
// Background Worker
var orderProcessor = builder.AddProject<Projects.ECommerce_OrderProcessor>("order-processor")
.WithReference(orderDb)
.WithReference(rabbitmq);
// Frontend
var web = builder.AddProject<Projects.ECommerce_Web>("web")
.WithReference(catalogApi)
.WithReference(orderApi);
builder.Build().Run();Catalog API (Products):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<CatalogDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("catalogdb")));
builder.Services.AddStackExchangeRedisCache(options =>
options.Configuration = builder.Configuration.GetConnectionString("cache"));
var app = builder.Build();
app.MapGet("/products", async (CatalogDbContext db, IDistributedCache cache) =>
{
var cached = await cache.GetStringAsync("products");
if (cached != null)
return JsonSerializer.Deserialize<List<Product>>(cached);
var products = await db.Products.ToListAsync();
await cache.SetStringAsync("products", JsonSerializer.Serialize(products),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });
return products;
});
app.MapGet("/products/{id}", async (int id, CatalogDbContext db) =>
await db.Products.FindAsync(id) is Product product ? Results.Ok(product) : Results.NotFound());
app.Run();Order API (Orders):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("orderdb")));
// RabbitMQ connection
builder.Services.AddSingleton<IConnectionFactory>(sp =>
new ConnectionFactory { Uri = new Uri(builder.Configuration.GetConnectionString("messaging")!) });
var app = builder.Build();
app.MapPost("/orders", async (Order order, OrderDbContext db, IConnectionFactory rabbitFactory) =>
{
db.Orders.Add(order);
await db.SaveChangesAsync();
// Publish order created event
using var connection = rabbitFactory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare("orders", durable: true, exclusive: false, autoDelete: false);
var message = JsonSerializer.Serialize(order);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish("", "orders", null, body);
return Results.Created($"/orders/{order.Id}", order);
});
app.MapGet("/orders/{id}", async (int id, OrderDbContext db) =>
await db.Orders.FindAsync(id) is Order order ? Results.Ok(order) : Results.NotFound());
app.Run();Order Processor (Background Worker):
public class Worker : BackgroundService
{
private readonly IConnectionFactory _rabbitFactory;
private readonly IServiceProvider _services;
public Worker(IConnectionFactory rabbitFactory, IServiceProvider services)
{
_rabbitFactory = rabbitFactory;
_services = services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var connection = _rabbitFactory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare("orders", durable: true, exclusive: false, autoDelete: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var order = JsonSerializer.Deserialize<Order>(message);
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();
var dbOrder = await db.Orders.FindAsync(order!.Id);
if (dbOrder != null)
{
dbOrder.Status = "Processing";
await db.SaveChangesAsync();
// Simulate processing
await Task.Delay(5000);
dbOrder.Status = "Completed";
await db.SaveChangesAsync();
}
channel.BasicAck(ea.DeliveryTag, false);
};
channel.BasicConsume("orders", autoAck: false, consumer);
await Task.Delay(Timeout.Infinite, stoppingToken);
}
}Test Complete System:
aspire run
# Dashboard shows all 8 services healthy
curl -X POST http://localhost:5001/orders \
-H "Content-Type: application/json" \
-d '{"userId":1,"productId":5,"quantity":2}'
# Order → Database → RabbitMQ → Worker → Processing → Completed
# Full trace visible in DashboardService Communication Flow:
Web Browser
↓ HTTP
Web (Blazor)
↓ HTTP
Catalog API → PostgreSQL (catalogdb) ← Cache (Redis)
Order API → PostgreSQL (orderdb) → RabbitMQ
↓ Message Queue
Order Processor → PostgreSQL (orderdb)Azure Deployment
Deploy Multi-Service Application to Azure
Prerequisites:
# Install Azure Developer CLI
curl -fsSL https://aka.ms/install-azd.sh | bash
# Login to Azure
az login
azd auth loginInitialize Deployment:
cd ECommerce
azd init
# Prompts:
# Environment name: dev
# Azure subscription: [Select your subscription]
# Azure location: eastusDeploy:
azd up
# Creates:
# - Resource Group: rg-ecommerce-dev
# - Container Registry: crecommercedev
# - Container Apps Environment: cae-ecommerce-dev
# - Log Analytics Workspace
# - 4 Container Apps (catalog-api, order-api, order-processor, web)
# - Azure Cache for Redis
# - Azure Database for PostgreSQL (2 databases)
# - Azure Service Bus (replaces RabbitMQ)Generated Bicep (excerpts):
// Azure Cache for Redis (replaces AddRedis)
resource redis 'Microsoft.Cache/Redis@2023-08-01' = {
name: 'redis-ecommerce-dev'
location: location
properties: {
sku: { name: 'Basic', family: 'C', capacity: 1 }
enableNonSslPort: false
}
}
// Azure Database for PostgreSQL (replaces AddPostgres)
resource postgres 'Microsoft.DBforPostgreSQL/flexibleServers@2023-03-01-preview' = {
name: 'pg-ecommerce-dev'
location: location
properties: {
version: '15'
administratorLogin: 'pgadmin'
administratorLoginPassword: pgPassword
storage: { storageSizeGB: 32 }
}
}
// Container App (replaces AddProject)
resource catalogApi 'Microsoft.App/containerApps@2023-05-01' = {
name: 'catalog-api'
properties: {
configuration: {
ingress: { external: true, targetPort: 8080 }
secrets: [
{ name: 'redis-connection', value: redis.properties.hostName }
{ name: 'postgres-connection', value: postgresConnectionString }
]
}
template: {
containers: [{
name: 'catalog-api'
image: '${containerRegistry.properties.loginServer}/catalog-api:latest'
env: [
{ name: 'ConnectionStrings__cache', secretRef: 'redis-connection' }
{ name: 'ConnectionStrings__catalogdb', secretRef: 'postgres-connection' }
]
}]
scale: { minReplicas: 1, maxReplicas: 10 }
}
}
}Post-Deployment:
# Get deployed URLs
azd env get-values
# Outputs:
# WEB_URL=https://web.xxx.eastus.azurecontainerapps.io
# CATALOG_API_URL=https://catalog-api.xxx.eastus.azurecontainerapps.io
# ORDER_API_URL=https://order-api.xxx.eastus.azurecontainerapps.io
# Test production deployment
curl https://catalog-api.xxx.eastus.azurecontainerapps.io/productsDeploy Updates:
# Make code changes
# Deploy updates only (faster)
azd deployMultiple Environments:
# Create staging environment
azd env new staging
azd up
# Switch between environments
azd env select dev
azd deploy
azd env select staging
azd deployTear Down:
azd down # Deletes all Azure resourcesPython Integration
Polyglot Application with Python Service
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("cache");
// Python FastAPI service
var pythonApi = builder.AddExecutable("python-api", "python", ".")
.WithArgs("python_api/app.py")
.WithHttpEndpoint(port: 8000)
.WithReference(redis);
// .NET API
var dotnetApi = builder.AddProject<Projects.DotNetApi>("dotnet-api")
.WithReference(pythonApi)
.WithReference(redis);
builder.Build().Run();Python Dependencies (python_api/requirements.txt):
fastapi==0.115.0
uvicorn[standard]==0.32.0
redis==5.0.8Python Service (python_api/app.py):
import os
from fastapi import FastAPI
from redis import Redis
app = FastAPI()
redis_connection = os.getenv("ConnectionStrings__cache", "localhost:6379")
redis_client = Redis.from_url(f"redis://{redis_connection}")
@app.get("/python/data")
def get_data():
value = redis_client.get("python-data")
return {"data": value.decode() if value else None}
@app.post("/python/data")
def set_data(value: str):
redis_client.set("python-data", value)
return {"status": "ok"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000).NET API Calls Python:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("python", client =>
{
client.BaseAddress = new Uri(builder.Configuration.GetConnectionString("python-api")!);
});
var app = builder.Build();
app.MapGet("/combined", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("python");
var response = await client.GetAsync("/python/data");
var data = await response.Content.ReadAsStringAsync();
return new { from_python = data, from_dotnet = "Hello from .NET" };
});
app.Run();Test:
aspire run
# Set data in Python API
curl -X POST http://localhost:8000/python/data?value=test
# Get combined data from .NET API
curl http://localhost:5000/combined
# Returns: {"from_python":"{\"data\":\"test\"}","from_dotnet":"Hello from .NET"}Node.js Integration
.NET + Node.js Express API
See Node.js integration guide.
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("db").AddDatabase("appdb");
// Node.js Express service
var nodeApi = builder.AddExecutable("node-api", "node", "node_api")
.WithArgs("server.js")
.WithHttpEndpoint(port: 3000)
.WithReference(postgres);
var dotnetApi = builder.AddProject<Projects.DotNetApi>("dotnet-api")
.WithReference(nodeApi)
.WithReference(postgres);
builder.Build().Run();Node.js Dependencies (node_api/package.json):
{
"dependencies": {
"express": "^4.19.2",
"pg": "^8.12.0"
}
}Node.js Service (node_api/server.js):
const express = require("express");
const { Pool } = require("pg");
const app = express();
const connectionString = process.env.ConnectionStrings__appdb || "postgresql://localhost/appdb";
const pool = new Pool({ connectionString });
app.get("/node/users", async (req, res) => {
const result = await pool.query("SELECT * FROM users");
res.json(result.rows);
});
app.listen(3000, () => {
console.log("Node.js API listening on port 3000");
});Test:
aspire run
curl http://localhost:3000/node/users
curl http://localhost:5000/users # Proxies to Node.jsGo Integration
API with Go Fiber Framework
See Go integration patterns (concepts apply to all languages).
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("cache");
// Go Fiber API service
var goApi = builder.AddExecutable("go-api", "go", "go_api")
.WithArgs("run", "main.go")
.WithHttpEndpoint(port: 8080)
.WithReference(redis);
var dotnetApi = builder.AddProject<Projects.DotNetApi>("dotnet-api")
.WithReference(goApi)
.WithReference(redis);
builder.Build().Run();Go Dependencies (go_api/go.mod):
module go-api
go 1.21
require (
github.com/gofiber/fiber/v2 v2.52.0
github.com/redis/go-redis/v9 v9.5.1
)Go Service (go_api/main.go):
package main
import (
"context"
"log"
"os"
"github.com/gofiber/fiber/v2"
"github.com/redis/go-redis/v9"
)
func main() {
app := fiber.New()
ctx := context.Background()
// Get Redis connection from Aspire
redisAddr := os.Getenv("ConnectionStrings__cache")
if redisAddr == "" {
redisAddr = "localhost:6379"
}
rdb := redis.NewClient(&redis.Options{
Addr: redisAddr,
})
app.Get("/go/data", func(c *fiber.Ctx) error {
val, err := rdb.Get(ctx, "go-data").Result()
if err == redis.Nil {
return c.JSON(fiber.Map{"data": nil})
} else if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": val})
})
app.Post("/go/data", func(c *fiber.Ctx) error {
value := c.Query("value")
err := rdb.Set(ctx, "go-data", value, 0).Err()
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"status": "ok"})
})
log.Fatal(app.Listen(":8080"))
}Test:
aspire run
# Set data in Go API
curl -X POST http://localhost:8080/go/data?value=hello
# Get data from Go API
curl http://localhost:8080/go/data
# Returns: {"data":"hello"}Custom Component Integration
Add Elasticsearch
AppHost:
var builder = DistributedApplication.CreateBuilder(args);
var elasticsearch = builder.AddContainer("elasticsearch", "elasticsearch")
.WithImageTag("8")
.WithEnvironment("discovery.type", "single-node")
.WithEnvironment("xpack.security.enabled", "false")
.WithHttpEndpoint(port: 9200, name: "http")
.WithDataVolume("elasticsearch-data");
var api = builder.AddProject<Projects.SearchApi>("api")
.WithReference(elasticsearch);
builder.Build().Run();API Integration:
builder.Services.AddSingleton<ElasticClient>(sp =>
{
var elasticUrl = builder.Configuration.GetConnectionString("elasticsearch");
var settings = new ConnectionSettings(new Uri(elasticUrl!));
return new ElasticClient(settings);
});
app.MapGet("/search", async (string query, ElasticClient elastic) =>
{
var response = await elastic.SearchAsync<Document>(s => s
.Query(q => q.Match(m => m.Field(f => f.Content).Query(query))));
return response.Documents;
});Resources
More Examples:
- Official Aspire Samples - eShop, Orleans, Dapr integrations
- Community Samples - GitHub projects using Aspire
- Component Documentation - All available integrations
Language Guides:
Aspire Production Patterns
Best practices, production deployment strategies, and anti-patterns for .NET Aspire.
See also: Deployment overview for complete Azure deployment strategies.
Production Deployment Patterns
High Availability Configuration
Multi-Replica Services:
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("appdb");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WithReference(postgres)
.WithReplicas(3); // 3 instances for HA
builder.Build().Run();Azure Deployment Result: Container App scales 1-3 replicas with load balancing, health checks, automatic failover
Database High Availability:
if (builder.Environment.IsProduction())
{
var postgres = builder.AddPostgres("db")
.WithHighAvailability() // Enables replication
.WithBackupRetention(days: 35)
.AddDatabase("appdb");
}
else
{
var postgres = builder.AddPostgres("db")
.WithDataVolume()
.AddDatabase("appdb");
}Multi-Region Deployment
Primary + Read Replicas:
var builder = DistributedApplication.CreateBuilder(args);
// Primary database (writes)
var primaryDb = builder.AddPostgres("db-primary")
.WithHighAvailability()
.AddDatabase("appdb");
// Read replicas (reads)
var replicaEast = builder.AddPostgres("db-replica-east")
.WithReplicaOf(primaryDb);
var replicaWest = builder.AddPostgres("db-replica-west")
.WithReplicaOf(primaryDb);
// API in East region
var apiEast = builder.AddProject<Projects.Api>("api-east")
.WithReference(primaryDb) // Writes
.WithReference(replicaEast) // Reads
.WithReplicas(3);
// API in West region
var apiWest = builder.AddProject<Projects.Api>("api-west")
.WithReference(primaryDb) // Writes
.WithReference(replicaWest) // Reads
.WithReplicas(3);Application Code (CQRS Pattern):
public class DatabaseService
{
private readonly AppDbContext _writeDb;
private readonly AppDbContext _readDb;
public DatabaseService(
[FromKeyedServices("primary")] AppDbContext writeDb,
[FromKeyedServices("replica")] AppDbContext readDb)
{
_writeDb = writeDb;
_readDb = readDb;
}
public async Task<User> GetUserAsync(int id) =>
await _readDb.Users.FindAsync(id); // Read from replica
public async Task CreateUserAsync(User user)
{
_writeDb.Users.Add(user);
await _writeDb.SaveChangesAsync(); // Write to primary
}
}Load Balancing Strategy
Geographic Load Balancing:
// Azure Front Door configuration
if (builder.Environment.IsProduction())
{
var frontDoor = builder.AddAzureFrontDoor("cdn")
.WithOrigin("api-east", apiEast)
.WithOrigin("api-west", apiWest)
.WithRoutingPolicy(RoutingPolicy.Performance); // Route to nearest region
var web = builder.AddProject<Projects.Web>("web")
.WithReference(frontDoor);
}Security Best Practices
See security overview for complete security guidance.
Secrets Management
Local Development (User Secrets):
dotnet user-secrets init
dotnet user-secrets set "ApiKeys:External" "dev-api-key-12345"Production (Azure Key Vault):
var builder = DistributedApplication.CreateBuilder(args);
var keyVault = builder.AddAzureKeyVault("vault");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(keyVault); // Managed identity access granted
builder.Build().Run();API Access to Secrets:
var builder = WebApplication.CreateBuilder(args);
// Aspire automatically configures Key Vault with managed identity
var externalApiKey = builder.Configuration["ApiKeys:External"];
builder.Services.AddHttpClient("external", client =>
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {externalApiKey}");
});Never Store Secrets in Code:
// ❌ BAD - Hardcoded secret
var apiKey = "sk-12345-secret";
// ✅ GOOD - From configuration
var apiKey = builder.Configuration["ApiKeys:External"];Managed Identity Pattern
Database Access Without Connection Strings:
var builder = DistributedApplication.CreateBuilder(args);
if (builder.Environment.IsProduction())
{
// Azure SQL with managed identity
var sqlDb = builder.AddAzureSqlDatabase("db")
.WithManagedIdentity(); // No password needed
var api = builder.AddProject<Projects.Api>("api")
.WithReference(sqlDb); // Identity granted db_datareader, db_datawriter
}
else
{
// Local with connection string
var sqlDb = builder.AddSqlServer("sql").AddDatabase("db");
var api = builder.AddProject<Projects.Api>("api").WithReference(sqlDb);
}API Configuration:
builder.Services.AddDbContext<AppDbContext>(options =>
{
var connection = builder.Configuration.GetConnectionString("db");
options.UseSqlServer(connection, sqlOptions =>
{
if (builder.Environment.IsProduction())
{
// Managed identity authentication
sqlOptions.UseAzureIdentity();
}
});
});Network Isolation
Private Endpoints:
if (builder.Environment.IsProduction())
{
var vnet = builder.AddAzureVirtualNetwork("vnet");
var postgres = builder.AddPostgres("db")
.WithPrivateEndpoint(vnet) // Not exposed to internet
.AddDatabase("appdb");
var api = builder.AddProject<Projects.Api>("api")
.WithVirtualNetwork(vnet) // Inside VNet
.WithReference(postgres); // Private communication
}API Management Gateway:
var apiManagement = builder.AddAzureApiManagement("apim")
.WithPolicy(new RateLimitPolicy(requestsPerMinute: 100))
.WithPolicy(new IpFilterPolicy(allowedIps: ["10.0.0.0/8"]));
var api = builder.AddProject<Projects.Api>("api")
.WithReference(postgres)
.ExposeVia(apiManagement); // All traffic goes through APIMPerformance Optimization
Connection Pooling
Database Connection Pools:
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("db"), npgsqlOptions =>
{
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 3);
npgsqlOptions.CommandTimeout(30);
npgsqlOptions.MinPoolSize(5); // Min connections
npgsqlOptions.MaxPoolSize(100); // Max connections
});
});Redis Connection Multiplexing:
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var connection = builder.Configuration.GetConnectionString("cache");
return ConnectionMultiplexer.Connect(new ConfigurationOptions
{
EndPoints = { connection! },
ConnectRetry = 3,
ReconnectRetryPolicy = new ExponentialRetry(5000),
AbortOnConnectFail = false
});
});Caching Strategy
Multi-Level Caching:
public class CatalogService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _redisCache;
private readonly AppDbContext _db;
public async Task<Product?> GetProductAsync(int id)
{
if (_memoryCache.TryGetValue($"product:{id}", out Product? product))
return product;
var cached = await _redisCache.GetStringAsync($"product:{id}");
if (cached != null)
{
product = JsonSerializer.Deserialize<Product>(cached);
_memoryCache.Set($"product:{id}", product, TimeSpan.FromMinutes(1));
return product;
}
product = await _db.Products.FindAsync(id);
if (product != null)
{
await _redisCache.SetStringAsync($"product:{id}",
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
_memoryCache.Set($"product:{id}", product, TimeSpan.FromMinutes(1));
}
return product;
}
}Cache Invalidation:
public async Task UpdateProductAsync(Product product)
{
_db.Products.Update(product);
await _db.SaveChangesAsync();
_memoryCache.Remove($"product:{product.Id}");
await _redisCache.RemoveAsync($"product:{product.Id}");
await _messageBus.PublishAsync(new CacheInvalidationEvent
{
CacheKey = $"product:{product.Id}"
});
}Asynchronous Processing
Background Jobs Pattern:
var builder = DistributedApplication.CreateBuilder(args);
var rabbitmq = builder.AddRabbitMQ("queue");
var postgres = builder.AddPostgres("db").AddDatabase("appdb");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(postgres)
.WithReference(rabbitmq);
var worker = builder.AddProject<Projects.Worker>("worker")
.WithReference(postgres)
.WithReference(rabbitmq)
.WithReplicas(5);
builder.Build().Run();API Publishes Job:
app.MapPost("/process", async (ProcessRequest request, IMessageBus bus) =>
{
var jobId = Guid.NewGuid();
await bus.PublishAsync(new ProcessJob { JobId = jobId, Data = request.Data });
return Results.Accepted($"/jobs/{jobId}", new { jobId });
});
app.MapGet("/jobs/{jobId}", async (Guid jobId, AppDbContext db) =>
{
var job = await db.Jobs.FindAsync(jobId);
return job != null ? Results.Ok(job) : Results.NotFound();
});Worker Processes Asynchronously:
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var job in _messageBus.ConsumeAsync<ProcessJob>(stoppingToken))
{
var result = await ProcessAsync(job.Data);
var dbJob = await _db.Jobs.FindAsync(job.JobId);
dbJob.Status = "Completed";
dbJob.Result = result;
await _db.SaveChangesAsync();
}
}
}Monitoring and Observability
Custom Metrics
Export Business Metrics:
public class OrderService
{
private readonly Counter<int> _orderCounter;
private readonly Histogram<double> _orderValue;
public OrderService(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("ECommerce.Orders");
_orderCounter = meter.CreateCounter<int>("orders.created");
_orderValue = meter.CreateHistogram<double>("orders.value");
}
public async Task CreateOrderAsync(Order order)
{
await _db.Orders.AddAsync(order);
await _db.SaveChangesAsync();
_orderCounter.Add(1, new KeyValuePair<string, object?>("status", "success"));
_orderValue.Record(order.TotalAmount);
}
}Dashboard: Metrics tab shows orders.created counter and orders.value distribution with percentiles (p50, p95, p99)
Distributed Tracing
Custom Spans:
public class CatalogService
{
private readonly ActivitySource _activitySource;
public CatalogService()
{
_activitySource = new ActivitySource("ECommerce.Catalog");
}
public async Task<Product?> GetProductAsync(int id)
{
using var activity = _activitySource.StartActivity("GetProduct");
activity?.SetTag("product.id", id);
var product = await _db.Products.FindAsync(id);
activity?.SetTag("product.found", product != null);
activity?.SetTag("product.category", product?.Category);
return product;
}
}Trace Propagation: Automatic across HTTP calls, visible in Dashboard
var client = _httpClientFactory.CreateClient("catalog-api");
var response = await client.GetAsync("/products/123");Structured Logging
Rich Logging:
_logger.LogInformation(
"Order {OrderId} created by user {UserId} with {ItemCount} items totaling {TotalAmount:C}",
order.Id, order.UserId, order.Items.Count, order.TotalAmount);
// Dashboard shows structured fields: OrderId, UserId, ItemCount, TotalAmountLog Correlation:
using (_logger.BeginScope(new Dictionary<string, object>
{
["TransactionId"] = transactionId,
["CorrelationId"] = correlationId
}))
{
_logger.LogInformation("Processing payment");
await _paymentService.ProcessAsync();
_logger.LogInformation("Payment processed");
}Polyglot Service Communication Patterns
HTTP Communication
Pattern: REST API between services
// AppHost - Python API calling Node.js service
var nodeApi = builder.AddExecutable("node-api", "node", ".")
.WithArgs("server.js")
.WithHttpEndpoint(port: 3000, name: "http");
var pythonApi = builder.AddExecutable("python-api", "python", ".")
.WithArgs("app.py")
.WithReference(nodeApi)
.WithHttpEndpoint(port: 8000);Python Service (FastAPI):
from fastapi import FastAPI
import httpx
import os
app = FastAPI()
# Aspire injects: services__node_api__http__0=http://localhost:3000
node_url = os.environ.get("services__node_api__http__0")
@app.get("/users/{user_id}")
async def get_user(user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"{node_url}/users/{user_id}")
return response.json()Node.js Service (Express):
const express = require("express");
const app = express();
app.get("/users/:id", (req, res) => {
res.json({ id: req.params.id, name: "John Doe" });
});
app.listen(3000);gRPC Communication
Pattern: High-performance RPC between services
// AppHost - Go gRPC server with C# client
var grpcServer = builder.AddExecutable("grpc-server", "go", ".")
.WithArgs("run", "server.go")
.WithHttpEndpoint(port: 9000, name: "grpc");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(grpcServer);Go gRPC Server:
// server.go
package main
import (
"context"
"net"
"google.golang.org/grpc"
pb "myapp/proto"
)
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
return &pb.UserResponse{Id: req.Id, Name: "John Doe"}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":9000")
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
s.Serve(lis)
}C# gRPC Client:
var grpcUrl = builder.Configuration["services:grpc-server:grpc:0"];
var channel = GrpcChannel.ForAddress(grpcUrl!);
var client = new UserService.UserServiceClient(channel);
var response = await client.GetUserAsync(new UserRequest { Id = 123 });Message Queue Communication
Pattern: Async communication with RabbitMQ
// AppHost - Polyglot services with RabbitMQ
var rabbitmq = builder.AddRabbitMQ("messaging");
var pythonProducer = builder.AddExecutable("producer", "python", ".")
.WithArgs("producer.py")
.WithReference(rabbitmq);
var nodeConsumer = builder.AddExecutable("consumer", "node", ".")
.WithArgs("consumer.js")
.WithReference(rabbitmq);Python Producer (pika):
import pika
import os
import json
rabbitmq_url = os.environ.get("ConnectionStrings__messaging")
params = pika.URLParameters(rabbitmq_url)
connection = pika.BlockingConnection(params)
channel = connection.channel()
channel.queue_declare(queue='tasks')
channel.basic_publish(exchange='', routing_key='tasks',
body=json.dumps({'task': 'process', 'data': 'value'}))Node.js Consumer (amqplib):
const amqp = require("amqplib");
const rabbitmqUrl = process.env.ConnectionStrings__messaging;
const connection = await amqp.connect(rabbitmqUrl);
const channel = await connection.createChannel();
await channel.assertQueue("tasks");
channel.consume("tasks", (msg) => {
const task = JSON.parse(msg.content.toString());
console.log("Processing:", task);
channel.ack(msg);
});Redis Pub/Sub Communication
Pattern: Event broadcasting across services
var redis = builder.AddRedis("cache");
var publisher = builder.AddExecutable("publisher", "python", ".")
.WithArgs("publisher.py")
.WithReference(redis);
var subscriber = builder.AddProject<Projects.Subscriber>("subscriber")
.WithReference(redis);Python Publisher:
import redis
import os
r = redis.from_url(os.environ.get("ConnectionStrings__cache"))
r.publish('events', 'user.created:123')C# Subscriber:
var redis = ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("cache")!);
var subscriber = redis.GetSubscriber();
await subscriber.SubscribeAsync("events", (channel, message) =>
{
Console.WriteLine($"Event received: {message}");
});Language-Specific Best Practices
Python Services
Async/Await Pattern:
# Use asyncio for I/O-bound operations
import asyncio
import aioredis
import asyncpg
async def process_request():
redis = await aioredis.from_url(os.environ.get("ConnectionStrings__cache"))
db = await asyncpg.connect(os.environ.get("ConnectionStrings__db"))
# Parallel I/O operations
user, cache_data = await asyncio.gather(
db.fetchrow("SELECT * FROM users WHERE id=$1", user_id),
redis.get(f"user:{user_id}")
)Environment Variable Handling:
# Aspire uses double underscore for nested config
# ConnectionStrings__cache → ConnectionStrings:cache
redis_conn = os.environ.get("ConnectionStrings__cache")
db_conn = os.environ.get("ConnectionStrings__db")
# Service endpoints use services__ prefix
api_url = os.environ.get("services__api__http__0")Node.js Services
Event Loop Optimization:
// Use async/await for non-blocking I/O
const redis = require("redis");
const { Pool } = require("pg");
const redisClient = redis.createClient({
url: process.env.ConnectionStrings__cache,
});
const pgPool = new Pool({
connectionString: process.env.ConnectionStrings__db,
});
app.get("/users/:id", async (req, res) => {
// Non-blocking parallel queries
const [user, cachedData] = await Promise.all([
pgPool.query("SELECT * FROM users WHERE id=$1", [req.params.id]),
redisClient.get(`user:${req.params.id}`),
]);
res.json(user.rows[0]);
});Graceful Shutdown:
// Handle SIGTERM from Aspire orchestration
process.on("SIGTERM", async () => {
console.log("SIGTERM received, shutting down gracefully");
await pgPool.end();
await redisClient.quit();
process.exit(0);
});Go Services
Goroutine Management:
// Use context for cancellation propagation
func handleRequest(ctx context.Context, db *sql.DB, redis *redis.Client) error {
// Use goroutines for parallel operations
var user User
var cacheData string
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
return db.QueryRowContext(ctx, "SELECT * FROM users WHERE id=$1", id).Scan(&user)
})
errGroup.Go(func() error {
cacheData, err = redis.Get(ctx, fmt.Sprintf("user:%d", id)).Result()
return err
})
return errGroup.Wait()
}Environment Configuration:
// Read Aspire-injected connection strings
redisConn := os.Getenv("ConnectionStrings__cache")
dbConn := os.Getenv("ConnectionStrings__db")
// Parse and connect
redisClient := redis.NewClient(&redis.Options{
Addr: redisConn,
})
db, _ := sql.Open("postgres", dbConn)Development Workflow Patterns
Hot Reload per Language
C# (Built-in):
# Automatic hot reload with dotnet watch
aspire run # Hot reload enabled by defaultPython (with watchdog):
# Add to Dockerfile or startup script
pip install watchdog
watchmedo auto-restart --patterns="*.py" --recursive -- python app.pyNode.js (with nodemon):
// package.json
{
"scripts": {
"dev": "nodemon server.js"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}AppHost Configuration:
if (builder.Environment.IsDevelopment())
{
builder.AddExecutable("node-api", "npm", ".")
.WithArgs("run", "dev"); // Uses nodemon
}
else
{
builder.AddExecutable("node-api", "node", ".")
.WithArgs("server.js");
}Debugging Polyglot Applications
Attach Debugger to Specific Service:
Python (VS Code):
// .vscode/launch.json
{
"name": "Attach to Python API",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
}
}Start Python service with debugpy:
# app.py
import debugpy
debugpy.listen(5678)
# debugpy.wait_for_client() # Uncomment to wait for debuggerNode.js (VS Code):
// .vscode/launch.json
{
"name": "Attach to Node API",
"type": "node",
"request": "attach",
"port": 9229
}Start Node.js with inspect:
// AppHost
builder.AddExecutable("node-api", "node", ".").WithArgs("--inspect=9229", "server.js");Go (Delve):
# Install delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Start with debugger
dlv debug --headless --listen=:2345 --api-version=2Shared Configuration Pattern
appsettings.json (shared config):
{
"Logging": {
"LogLevel": { "Default": "Information" }
},
"ConnectionStrings": {
"external-api": "https://api.external.com"
}
}Read in Python:
import json
with open('appsettings.json') as f:
config = json.load(f)
external_api = config['ConnectionStrings']['external-api']Read in Node.js:
const config = require("./appsettings.json");
const externalApi = config.ConnectionStrings["external-api"];Read in Go:
import "encoding/json"
type Config struct {
ConnectionStrings map[string]string `json:"ConnectionStrings"`
}
file, _ := os.Open("appsettings.json")
var config Config
json.NewDecoder(file).Decode(&config)
externalApi := config.ConnectionStrings["external-api"]Polyglot Anti-Patterns
❌ Language-Specific Serialization Pitfalls
Bad - Python datetime to JSON:
# BAD - Python datetime not JSON serializable
import datetime
data = {'timestamp': datetime.datetime.now()}
json.dumps(data) # ERROR: datetime not serializableGood - ISO 8601 strings:
# GOOD - Use ISO 8601 strings
data = {'timestamp': datetime.datetime.now().isoformat()}
json.dumps(data) # Works across all languages❌ Async/Sync Boundary Violations
Bad - Blocking in async context:
# BAD - Blocking I/O in async function
async def get_user(user_id):
response = requests.get(f"http://api/users/{user_id}") # Blocks event loop
return response.json()Good - Use async libraries:
# GOOD - Non-blocking async I/O
async def get_user(user_id):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://api/users/{user_id}")
return response.json()❌ Inconsistent Error Handling
Bad - Language-specific error formats:
# Python returns dict
{"error": "Not found", "code": 404}
# Node.js returns different format
{"message": "Not found", "statusCode": 404}Good - Standardized error format:
{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"statusCode": 404,
"timestamp": "2026-01-28T12:00:00Z"
}
}❌ Hardcoded Service URLs
Bad - Hardcoded endpoints:
# BAD - Hardcoded URL breaks in different environments
api_url = "http://localhost:3000/users"Good - Environment-based discovery:
# GOOD - Use Aspire service discovery
api_url = os.environ.get("services__node_api__http__0")
users_endpoint = f"{api_url}/users"❌ Missing Health Checks
Bad - No health endpoint:
# BAD - Service has no health check
app = FastAPI()
# No /health endpointGood - Implement health checks:
# GOOD - Health endpoint for DCP monitoring
@app.get("/health")
async def health():
return {"status": "healthy", "service": "python-api"}Quick Reference: Polyglot Patterns
| Pattern | Use Case | Languages | Latency | Complexity |
|---|---|---|---|---|
| HTTP REST | Public APIs, CRUD operations | All | 5-50ms | Low |
| gRPC | Internal services, high throughput | C#, Go, Python | 1-10ms | Medium |
| Message Queue | Async tasks, decoupling | All | 10-100ms | Medium |
| Redis Pub/Sub | Real-time events, broadcasting | All | 1-5ms | Low |
| Shared Database | Data consistency (use sparingly) | All | 1-10ms | Low |
Recommendation: Start with HTTP REST, migrate to gRPC for performance-critical paths, use message queues for long-running tasks.
Anti-Patterns (What NOT to Do)
❌ Hardcoded Connection Strings
// BAD - Bypasses Aspire service discovery
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql("Host=localhost;Database=mydb"));
// GOOD - Uses Aspire-managed connection
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("db")));❌ Manual Container Management
// BAD - Starting containers manually
Process.Start("docker", "run -p 6379:6379 redis");
// GOOD - Let Aspire manage it
builder.AddRedis("cache");❌ Bypassing ServiceDefaults
// BAD - Custom telemetry configuration
builder.Services.AddOpenTelemetry()
.WithTracing(/* manual config */);
// GOOD - Use ServiceDefaults for shared configuration
builder.AddServiceDefaults(); // Includes telemetry, health checks, resilience❌ Ignoring Health Checks
// BAD - No health check
builder.AddProject<Projects.Api>("api");
// GOOD - Add health checks
var api = builder.AddProject<Projects.Api>("api");
// In API project:
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>()
.AddRedis(builder.Configuration.GetConnectionString("cache")!);
app.MapHealthChecks("/health");❌ Synchronous Blocking Calls
// BAD - Blocking I/O
var product = _db.Products.Find(id); // Blocks thread
var response = _httpClient.GetAsync(url).Result; // Deadlock risk
// GOOD - Async all the way
var product = await _db.Products.FindAsync(id);
var response = await _httpClient.GetAsync(url);❌ Missing Retry Policies
// BAD - No resilience
builder.Services.AddHttpClient("external", client => { /* config */ });
// GOOD - Add retry and circuit breaker
builder.Services.AddHttpClient("external", client => { /* config */ })
.AddStandardResilienceHandler(); // From ServiceDefaults
// Or custom policy:
builder.Services.AddHttpClient("external")
.AddPolicyHandler(Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));❌ Single Point of Failure
// BAD - Single instance in production
var api = builder.AddProject<Projects.Api>("api");
// GOOD - Multiple replicas
var api = builder.AddProject<Projects.Api>("api")
.WithReplicas(3); // High availability❌ Ignoring Environment Differences
// BAD - Same config for all environments
var postgres = builder.AddPostgres("db").AddDatabase("appdb");
// GOOD - Environment-specific config
var postgres = builder.Environment.IsProduction()
? builder.AddAzurePostgres("db").WithHighAvailability()
: builder.AddPostgres("db").WithDataVolume();
var db = postgres.AddDatabase("appdb");❌ Missing Resource Limits
// BAD - Unbounded resource usage
builder.AddContainer("worker", "my-worker");
// GOOD - Set limits
builder.AddContainer("worker", "my-worker")
.WithAnnotation(new ResourceLimits
{
CpuLimit = 1.0,
MemoryLimit = "512Mi"
});❌ Not Using Dashboard
// BAD - Adding custom logging infrastructure
builder.Services.AddSerilog(); // Unnecessary complexity
// GOOD - Use built-in Dashboard
// Aspire Dashboard already provides:
// - Structured logging
// - Distributed tracing
// - Metrics visualization
// - Resource monitoringMigration Strategies
Docker Compose → Aspire
Old (docker-compose.yml):
services:
api:
build: ./api
ports:
- "5000:80"
environment:
- ConnectionStrings__db=Host=postgres;Database=mydb
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7New (AppHost):
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("postgres")
.WithDataVolume()
.AddDatabase("mydb");
var redis = builder.AddRedis("redis")
.WithDataVolume();
var api = builder.AddProject<Projects.Api>("api")
.WithReference(postgres)
.WithReference(redis);
builder.Build().Run();Benefits:
- Type-safe configuration
- Automatic service discovery
- Built-in observability
- Same code deploys to cloud
Kubernetes → Aspire
Aspire generates Kubernetes manifests via azd CLI. Migration path:
1. Convert Kubernetes Services → AddProject / AddContainer 2. Convert ConfigMaps → WithEnvironment / WithReference 3. Convert Secrets → Azure Key Vault integration 4. Convert Deployments → Aspire resource definitions
AppHost becomes the single source of truth for both local and cloud deployment.
Production Checklist
Before deploying to production:
- [ ] Health checks configured for all services
- [ ] Secrets moved to Azure Key Vault (no hardcoded values)
- [ ] Managed identities enabled (no connection string passwords)
- [ ] Resource limits set (CPU, memory)
- [ ] Replica counts configured (min 2 for HA)
- [ ] Retry and circuit breaker policies added
- [ ] Monitoring and alerts configured
- [ ] Database backups enabled
- [ ] Network isolation (VNet, private endpoints)
- [ ] Load testing completed
- [ ] Disaster recovery plan documented
Polyglot Service Communication
HTTP Communication (Recommended):
Services in different languages communicate via HTTP using service discovery:
// AppHost - Python and .NET services
var pythonApi = builder.AddExecutable("python-api", "python", ".").WithArgs("app.py").WithHttpEndpoint(port: 8000);
var dotnetApi = builder.AddProject<Projects.Api>("api").WithReference(pythonApi);// .NET calls Python service
builder.Services.AddHttpClient("python", client =>
{
client.BaseAddress = new Uri(builder.Configuration.GetConnectionString("python-api")!);
});
app.MapGet("/call-python", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("python");
return await client.GetStringAsync("/endpoint");
});# Python calls .NET service (reads connection string from environment)
import os
import httpx
dotnet_url = os.getenv("ConnectionStrings__api") # Injected by Aspire
async with httpx.AsyncClient() as client:
response = await client.get(f"{dotnet_url}/endpoint")Shared Infrastructure:
All services access databases, caches, queues via connection strings injected by AppHost—language-agnostic.
Learn more: Service discovery, Python integration, Node.js integration.
Resources
Production Guidance:
Advanced Topics:
Use these patterns to build robust, scalable, production-ready Aspire applications.
Aspire Technical Reference
Complete API reference and architectural details for .NET Aspire orchestration.
See also: Official API documentation for complete method signatures.
AppHost API Reference
DistributedApplication Builder
var builder = DistributedApplication.CreateBuilder(args);Properties:
builder.Configuration- IConfiguration for appsettings.jsonbuilder.Environment- IHostEnvironment (Development, Staging, Production)builder.Services- IServiceCollection for dependency injection
Methods:
builder.AddProject<TProject>(string name)- Add .NET projectbuilder.AddContainer(string name, string image)- Add containerbuilder.AddExecutable(string name, string command, string workingDirectory)- Add processbuilder.Build()- Build application.Run()- Start orchestration
Resource Types
See component overview for all available resource types.
AddProject
Adds a .NET project to the application model. API docs.
builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WithHttpEndpoint(port: 8080, name: "http")
.WithEnvironment("LOG_LEVEL", "Debug")
.WithReplicas(3);Configuration:
WithReference(IResourceBuilder)- Add dependency referenceWithHttpEndpoint(int? port, string? name)- HTTP endpointWithHttpsEndpoint(int? port, string? name)- HTTPS endpointWithEnvironment(string, string)- Environment variableWithEnvironment(Action<EnvironmentCallbackContext>)- Dynamic environmentWithReplicas(int)- Replica count for cloud deployment
AddRedis
Adds Redis cache server. Component docs.
builder.AddRedis("cache")
.WithDataVolume() // Persistent storage
.WithRedisCommander() // Add Redis Commander UI
.WithRedisInsight() // Add RedisInsight UI
.WithImageTag("7.2-alpine") // Specific Redis version
.WithPersistence() // Enable RDB persistence
.WithPersistence(interval: 900, changesThreshold: 1) // Custom persistenceConfiguration:
WithDataVolume(string? name)- Mount volume for data persistenceWithRedisCommander(int? port)- Add Redis Commander web UIWithRedisInsight(int? port)- Add RedisInsight web UIWithImageTag(string tag)- Specify Docker image tagWithPersistence(int? interval, int? changesThreshold)- Configure RDB persistence
AddPostgres
Adds PostgreSQL database server. Component docs.
builder.AddPostgres("pg")
.WithDataVolume() // Persistent storage
.WithPgAdmin() // Add pgAdmin UI
.AddDatabase("mydb") // Create database
.AddDatabase("otherdb"); // Multiple databasesConfiguration:
WithDataVolume(string? name)- Mount volume for data persistenceWithPgAdmin(int? port)- Add pgAdmin web UIWithImageTag(string tag)- Specify Docker image tagAddDatabase(string name)- Create database (returns database resource)
Database Resource:
var db = postgres.AddDatabase("mydb");
builder.AddProject<Projects.Api>("api")
.WithReference(db); // Reference specific databaseAddSqlServer
Adds SQL Server database.
builder.AddSqlServer("sql")
.WithDataVolume()
.AddDatabase("mydb");Configuration: Similar to PostgreSQL.
AddMongoDB
Adds MongoDB database.
builder.AddMongoDB("mongo")
.WithDataVolume()
.WithMongoExpress() // Add Mongo Express UI
.AddDatabase("mydb");Configuration:
WithDataVolume(string? name)- Mount volume for data persistenceWithMongoExpress(int? port)- Add Mongo Express web UIAddDatabase(string name)- Create database
AddRabbitMQ
Adds RabbitMQ message broker.
builder.AddRabbitMQ("messaging")
.WithDataVolume()
.WithManagementPlugin(); // Enable management UIConfiguration:
WithDataVolume(string? name)- Mount volume for data persistenceWithManagementPlugin(int? port)- Enable RabbitMQ management UI
AddKafka
Adds Apache Kafka message broker.
builder.AddKafka("kafka")
.WithDataVolume()
.WithKafkaUI(); // Add Kafka UIConfiguration:
WithDataVolume(string? name)- Mount volume for data persistenceWithKafkaUI(int? port)- Add Kafka UI web interface
AddContainer
Adds generic Docker container.
builder.AddContainer("nginx", "nginx:latest")
.WithHttpEndpoint(port: 80, targetPort: 80)
.WithBindMount("./nginx.conf", "/etc/nginx/nginx.conf")
.WithVolume("nginx-data", "/data")
.WithEnvironment("NGINX_PORT", "80");Configuration:
WithHttpEndpoint(int?, int?, string?)- Expose HTTP portWithHttpsEndpoint(int?, int?, string?)- Expose HTTPS portWithBindMount(string, string, bool)- Mount host directoryWithVolume(string, string, bool)- Mount named volumeWithEnvironment(string, string)- Environment variable
AddExecutable
Adds executable process.
builder.AddExecutable("python-app", "python", ".")
.WithArgs("app.py", "--port", "8000")
.WithHttpEndpoint(port: 8000)
.WithEnvironment("PYTHONPATH", "/app");Configuration:
WithArgs(params string[])- Command argumentsWithHttpEndpoint(int?, string?)- Register HTTP endpointWithEnvironment(string, string)- Environment variable
AddConnectionString
References external service via connection string from appsettings.json.
// appsettings.json: "ConnectionStrings": { "external-api": "https://api.external.com" }
var externalApi = builder.AddConnectionString("external-api");
builder.AddProject<Projects.Api>("api").WithReference(externalApi);Use Cases: External APIs, cloud-managed databases, third-party services
Service Discovery & Environment Injection
See service discovery overview.
Automatic Connection String Generation
When you add a reference:
var redis = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(redis);AppHost generates:
// Environment variable automatically injected into API:
ConnectionStrings__cache = "localhost:6379" // Local
// or
ConnectionStrings__cache = "my-redis.redis.cache.windows.net:6380,ssl=True" // AzureService reads:
var connection = builder.Configuration.GetConnectionString("cache");
// Returns: "localhost:6379" or Azure Redis URLEnvironment Variable Naming
Reference name "cache" becomes:
- Connection string:
ConnectionStrings__cache - Configuration key:
ConnectionStrings:cache
Reference name "user-db" becomes:
- Connection string:
ConnectionStrings__user_db - Configuration key:
ConnectionStrings:user-db
Convention: Hyphens in names become underscores in environment variables.
Dynamic Environment Configuration
var api = builder.AddProject<Projects.Api>("api")
.WithEnvironment(context =>
{
// Access other resources
var redisEndpoint = context.ExecutionContext.IsPublishMode
? "my-redis.azure.com"
: "localhost:6379";
context.EnvironmentVariables["REDIS_URL"] = redisEndpoint;
context.EnvironmentVariables["ENVIRONMENT"] = builder.Environment.EnvironmentName;
});DCP Orchestration Internals
Developer Control Plane (DCP)
DCP is the orchestration engine managing resource lifecycles.
Architecture:
AppHost (Program.cs)
↓ defines
App Model (IResource graph)
↓ consumed by
DCP (Orchestrator)
↓ manages
Docker Containers + Processes + DatabasesDCP Operations:
1. Parse AppHost: Read Program.cs and build resource graph 2. Resolve Dependencies: Topological sort for startup order 3. Provision Resources: Start containers, processes in correct order 4. Inject Environment: Generate connection strings, inject into services 5. Health Monitoring: Poll health endpoints, restart on failure 6. Log Aggregation: Collect logs from all resources 7. Telemetry: Forward OpenTelemetry data to Dashboard
Resource Lifecycle States
Defined → Starting → Running → Healthy
↓
Failed → Restarting → RunningStates:
- Defined: Resource declared in AppHost
- Starting: Container/process launching
- Running: Process started, not yet healthy
- Healthy: Health check passing
- Failed: Startup failed or health check failing
- Restarting: Attempting automatic recovery
Dependency Resolution
var redis = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("mydb");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WithReference(postgres);Startup Order:
1. Redis container starts 2. PostgreSQL container starts 3. Wait for Redis and PostgreSQL health checks 4. API project starts with connection strings injected
Parallel Startup: Independent resources (Redis, PostgreSQL) start in parallel. Only dependent resources (API) wait.
Health Checks
Container Health:
builder.AddContainer("nginx", "nginx:latest")
.WithHealthCheck("http://localhost/health"); // HTTP endpointProject Health:
// ServiceDefaults automatically adds health checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy());DCP Behavior:
- Polls health endpoint every 10 seconds
- 3 consecutive failures → marks unhealthy
- Unhealthy → automatic restart
- Max 5 restarts before giving up
Configuration Options
Resource Limits
builder.AddContainer("nginx", "nginx:latest")
.WithAnnotation(new ResourceLimits
{
CpuLimit = 2.0, // CPU cores
MemoryLimit = "1Gi" // Memory
});Volumes and Persistence
Named Volumes:
builder.AddPostgres("db")
.WithDataVolume("postgres-data"); // Named volumeBind Mounts:
builder.AddContainer("nginx", "nginx:latest")
.WithBindMount("./config/nginx.conf", "/etc/nginx/nginx.conf", isReadOnly: true);Volume Lifecycle:
- Named volumes persist across
aspire runsessions - Bind mounts reference host filesystem
- Volumes deleted with
aspire down --volumes
Network Configuration
Port Mapping:
builder.AddContainer("nginx", "nginx:latest")
.WithHttpEndpoint(port: 8080, targetPort: 80); // Host:8080 → Container:80Service-to-Service Communication:
- Services communicate using service names (automatic DNS)
- Example:
http://api/users(no need for localhost:port)
Secrets Management
Local Development:
builder.Configuration.AddUserSecrets<Program>(); // User secretsCloud Deployment:
// Azure Key Vault automatically configured
builder.AddAzureKeyVault("vault");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(keyVault); // Managed identity accessDashboard API
Accessing Dashboard Programmatically
# Dashboard exposes REST API
GET http://localhost:15888/api/resources # List all resources
GET http://localhost:15888/api/logs?name=api # Get logs for resource
GET http://localhost:15888/api/metrics # Prometheus-compatible metricsOpenTelemetry Integration
Automatic Instrumentation:
- HTTP requests (client + server)
- Database queries (Entity Framework, Dapper)
- Message queues (RabbitMQ, Kafka)
- Redis operations
Custom Telemetry:
// ServiceDefaults automatically configures OpenTelemetry
using var activity = source.StartActivity("custom-operation");
activity?.SetTag("user.id", userId);Log Aggregation
Structured Logging:
logger.LogInformation("User {UserId} performed {Action}", userId, action);
// Appears in Dashboard with structured fieldsLog Levels:
- Trace, Debug, Information, Warning, Error, Critical
- Dashboard filters by level
Azure Deployment Details
Bicep Generation
azd deploy analyzes AppHost and generates Bicep templates:
// Generated from AddRedis("cache")
resource redis 'Microsoft.Cache/Redis@2023-08-01' = {
name: 'my-app-cache'
location: resourceGroup().location
properties: {
sku: { name: 'Basic', family: 'C', capacity: 1 }
}
}
// Generated from AddProject<Api>("api")
resource apiApp 'Microsoft.App/containerApps@2023-05-01' = {
name: 'my-app-api'
properties: {
configuration: {
secrets: [
{ name: 'redis-connection', value: redis.properties.hostName }
]
}
template: {
containers: [{
name: 'api'
image: 'myacr.azurecr.io/api:latest'
env: [
{ name: 'ConnectionStrings__cache', secretRef: 'redis-connection' }
]
}]
}
}
}Azure Resources Mapping
| Aspire Resource | Azure Resource | Notes |
|---|---|---|
| AddProject | Azure Container Apps | Serverless container hosting |
| AddRedis | Azure Cache for Redis | Managed Redis |
| AddPostgres | Azure Database for PostgreSQL | Managed PostgreSQL |
| AddSqlServer | Azure SQL Database | Managed SQL Server |
| AddMongoDB | Azure CosmosDB (MongoDB API) | Managed MongoDB |
| AddRabbitMQ | Azure Service Bus | Managed messaging |
| AddKafka | Azure Event Hubs | Kafka-compatible |
Managed Identity Configuration
// Local: uses connection strings
// Azure: automatically configures managed identity
var keyVault = builder.AddAzureKeyVault("vault");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(keyVault); // Managed identity granted Key Vault accessAzure Behavior:
- Container App gets system-assigned managed identity
- Identity granted access to Key Vault, databases, storage
- No connection strings or passwords in configuration
Advanced Patterns
Custom Resource Types
// Implement IResource for custom resources
public class CustomResource : IResource
{
public string Name { get; }
// Custom implementation
}
public static class CustomResourceExtensions
{
public static IResourceBuilder<CustomResource> AddCustom(
this IDistributedApplicationBuilder builder,
string name)
{
var resource = new CustomResource(name);
return builder.AddResource(resource);
}
}
// Usage:
builder.AddCustom("my-resource");Conditional Resources
if (builder.Environment.IsDevelopment())
{
builder.AddRedis("cache"); // Local Redis
}
else
{
builder.AddConnectionString("cache"); // Azure Redis (from config)
}Multi-Region Deployment
var primaryDb = builder.AddPostgres("db-primary");
var replicaDb = builder.AddPostgres("db-replica")
.WithReplicaOf(primaryDb);
var api = builder.AddProject<Projects.Api>("api")
.WithReference(primaryDb) // Write operations
.WithReference(replicaDb); // Read operationsResources
API Reference:
- Aspire.Hosting Namespace - Complete API documentation
- Component Overview - All available integrations
- AppHost Reference - Detailed AppHost guide
Advanced Topics:
See patterns.md for complete production deployment strategies.
Aspire Troubleshooting Guide
Common issues, debugging strategies, and solutions for .NET Aspire development.
See also: GitHub Issues for known problems and community solutions.
Orchestration Issues
Services Not Starting
Symptom: Dashboard shows service in "Starting" state indefinitely.
Diagnosis:
aspire run --verbose
# View service logs: Dashboard → Resources → [service-name] → LogsCommon Causes:
1. Port Conflict
Error: Failed to bind to address http://localhost:5000: address already in useSolution: Use dynamic port allocation
builder.AddProject<Projects.Api>("api").WithHttpEndpoint();2. Missing Dependencies
Error: Unable to connect to databaseSolution: Use WithReference to ensure dependencies start first
var redis = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("appdb");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WithReference(postgres);3. Health Check Timeout
Warning: Health check timeout after 30sSolution: Increase timeout or fix slow startup
builder.AddProject<Projects.Api>("api")
.WithHealthCheckTimeout(TimeSpan.FromSeconds(60));Services Fail Health Checks
Symptom: Service starts but marked unhealthy in Dashboard.
Diagnosis:
curl http://localhost:5000/health
# Dashboard → Resources → [service] → Health tabSolutions:
1. Add Health Endpoint
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>()
.AddRedis(builder.Configuration.GetConnectionString("cache")!);
app.MapHealthChecks("/health");2. Fix Database Connection
builder.Services.AddHealthChecks()
.AddNpgSql(builder.Configuration.GetConnectionString("db")!,
timeout: TimeSpan.FromSeconds(5),
failureStatus: HealthStatus.Unhealthy);3. Check Dependency Health
builder.Services.AddHealthChecks()
.AddRedis(builder.Configuration.GetConnectionString("cache")!, name: "redis-check");Startup Order Issues
Symptom: Service starts before dependencies are ready.
Example Error:
System.TimeoutException: Unable to connect to RedisSolution: Use WithReference to establish dependencies
var redis = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api").WithReference(redis);If still failing: Add retry logic
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("cache");
})
.AddResilience(policy => policy
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5,
Delay = TimeSpan.FromSeconds(2)
}));Dependency Conflicts
NuGet Package Version Mismatches
Symptom:
Error: Package 'Aspire.Hosting.Redis' 8.0.0 is not compatible with 'Aspire.Hosting' 8.1.0Solution: Ensure all Aspire packages use same version
# Check versions
dotnet list package
# Update all Aspire packages
dotnet add package Aspire.Hosting --version 8.1.0
dotnet add package Aspire.Hosting.Redis --version 8.1.0
dotnet add package Aspire.Hosting.PostgreSQL --version 8.1.0Or use Directory.Packages.props:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<AspireVersion>8.1.0</AspireVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Aspire.Hosting" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.Redis" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="$(AspireVersion)" />
</ItemGroup>
</Project>Docker Image Pull Failures
Symptom:
Error: Unable to pull image 'redis:latest': no such hostDiagnosis:
# Test Docker connectivity
docker pull redis:latest
# Check Docker daemon status
docker infoSolutions:
1. Docker Not Running: open -a Docker (macOS), sudo systemctl start docker (Linux), Start Docker Desktop (Windows)
2. Network Issues: Test connectivity with ping docker.io, configure proxy if needed
3. Use Specific Image Version:
builder.AddRedis("cache").WithImageTag("7.2-alpine");Missing Database Drivers
Symptom:
Error: No database provider has been configured for this DbContextSolution: Add appropriate NuGet package
# PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
# SQL Server
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
# MongoDB
dotnet add package MongoDB.DriverAnd configure:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("db")));Deployment Failures
Azure Deployment - Authentication Errors
Symptom:
Error: Failed to authenticate with Azure. Run 'azd auth login'Solution:
# Login to Azure
az login
azd auth login
# Verify authentication
az account show
azd env listAzure Deployment - Insufficient Permissions
Symptom:
Error: The client does not have authorization to perform action 'Microsoft.Resources/deployments/write'Solution: Grant required permissions
# Check current role
az role assignment list --assignee $(az account show --query user.name -o tsv)
# Assign Contributor role (requires admin)
az role assignment create \
--assignee user@example.com \
--role Contributor \
--scope /subscriptions/{subscription-id}Minimum Required Roles: Contributor (resource creation), User Access Administrator (managed identities)
Azure Deployment - Resource Quota Exceeded
Symptom:
Error: Operation could not be completed as it results in exceeding quota limitDiagnosis:
# Check current quota usage
az vm list-usage --location eastus -o table
az network vnet list --query "length([*])"Solutions:
1. Request Quota Increase: Azure Portal → Subscriptions → Usage + quotas → Submit request
2. Deploy to Different Region: azd env set AZURE_LOCATION westus2 && azd deploy
3. Reduce Resource Count: builder.AddProject<Projects.Api>("api").WithReplicas(1)
Azure Deployment - Container Build Failures
Symptom:
Error: Failed to build container image for 'api'Diagnosis:
# Test local build
docker build -t api:test .
# Check Dockerfile
cat DockerfileCommon Issues:
1. Missing Dockerfile: dotnet new dockerfile --name Dockerfile
2. Build Context Issues: Ensure correct paths in multi-stage Dockerfile (base → build → publish → final)
3. Registry Authentication: az acr login --name myregistry
Connection Issues
Service Discovery Not Working
Symptom: Service can't resolve other services by name.
Example Error:
HttpRequestException: No such host is known: apiDiagnosis:
// Log connection string to verify
_logger.LogInformation("Connecting to: {Connection}",
builder.Configuration.GetConnectionString("api"));Solutions:
1. Missing Reference: Add .WithReference(catalogApi) to dependent service
2. Wrong Connection String Name: Match GetConnectionString("name") with AppHost resource name
3. HTTP Client Configuration:
builder.Services.AddHttpClient("catalog", client =>
{
var baseAddress = builder.Configuration.GetConnectionString("catalog-api");
client.BaseAddress = new Uri(baseAddress!);
});Redis Connection Failures
Symptom:
RedisConnectionException: It was not possible to connect to the redis serverDiagnosis:
aspire run # Dashboard → Resources → cache → Status should be "Healthy"
redis-cli ping # Should return: PONGSolutions:
1. Wait for Redis Startup
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("cache");
})
.AddStandardResilienceHandler(); // Adds retry policy2. Check Connection String Format: Local localhost:6379, Azure host:6380,ssl=True,password=...
3. Enable Connection Multiplexing
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var connection = builder.Configuration.GetConnectionString("cache");
return ConnectionMultiplexer.Connect(connection!);
});Database Connection Timeouts
Symptom:
Npgsql.NpgsqlException: Connection timed outSolutions:
1. Increase Timeout
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(
builder.Configuration.GetConnectionString("db"),
npgsqlOptions => npgsqlOptions.CommandTimeout(60)));2. Add Retry Policy
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(
builder.Configuration.GetConnectionString("db"),
npgsqlOptions => npgsqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorCodesToAdd: null)));3. Check Database Health: Local: docker logs [container-id], Azure: az postgres flexible-server show
Dashboard Issues
Dashboard Not Opening
Symptom: aspire run succeeds but Dashboard doesn't open in browser.
Solutions:
1. Manual Navigation: aspire run shows URL, open http://localhost:15888 manually
2. Port Conflict: Check with lsof -i :15888 or netstat, use aspire run --dashboard-port 16000
Missing Telemetry Data
Symptom: Dashboard shows no traces or metrics for services.
Solutions:
1. Add ServiceDefaults:
builder.AddServiceDefaults(); // Adds OpenTelemetry
var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();2. Verify OpenTelemetry Configuration: Check ServiceDefaults/Extensions.cs configures .WithTracing() and .WithMetrics()
3. Check Dashboard Connection: Service logs should show OpenTelemetry exporting to http://localhost:4317
Performance Issues
Slow Service Startup
Symptom: Services take minutes to start.
Diagnosis:
# Profile startup time
dotnet run --project MyApp.AppHost
# Note timestamps in console outputSolutions:
1. Reduce Container Image Size: Use Alpine-based images (mcr.microsoft.com/dotnet/aspnet:8.0-alpine)
2. Parallel Startup: Independent services start in parallel automatically
3. Use Existing Containers: builder.AddConnectionString("cache") for already-running services
High Memory Usage
Symptom: Dashboard shows services using excessive memory.
Diagnosis: docker stats or Dashboard → Resources → [service] → Metrics → Memory
Solutions:
1. Set Resource Limits
builder.AddContainer("api", "my-api")
.WithAnnotation(new ResourceLimits
{
MemoryLimit = "512Mi"
});2. Optimize Application: Use HttpClient factory, enable connection pooling with .MaxPoolSize(50)
Environment-Specific Issues
Differences Between Local and Cloud
Symptom: Works locally but fails in Azure.
Common Causes:
1. Environment Detection
if (builder.Environment.IsDevelopment())
{
// Local: uses AddRedis
builder.AddRedis("cache");
}
else
{
// Cloud: uses Azure Redis (from config)
builder.AddConnectionString("cache");
}2. Managed Identity Not Configured
// Local: uses connection string with password
// Azure: uses managed identity (no password)
builder.Services.AddDbContext<AppDbContext>(options =>
{
var connection = builder.Configuration.GetConnectionString("db");
options.UseSqlServer(connection, sqlOptions =>
{
if (!builder.Environment.IsDevelopment())
{
sqlOptions.UseAzureIdentity(); // Managed identity
}
});
});3. Missing Environment Variables
# Set in Azure Container App
az containerapp update \
--name my-api \
--set-env-vars "FeatureFlags__NewUI=true"Getting Help
Enable Verbose Logging
# Run with verbose output
aspire run --verbose
# Set log level
export Logging__LogLevel__Default=Debug
aspire runCollect Diagnostic Information
# Export Dashboard data
curl http://localhost:15888/api/resources > resources.json
curl http://localhost:15888/api/logs > logs.txt
# Collect Docker logs
docker-compose logs > docker-logs.txt
# Azure deployment logs
az containerapp logs show --name my-api --resource-group mygroupCommon Log Patterns to Search For
| Pattern | Meaning |
|---|---|
Failed to bind to address | Port conflict |
Unable to connect to | Dependency not ready |
Health check timeout | Service not responding |
Authentication failed | Credentials issue |
No such host | Service discovery problem |
Connection refused | Service not listening |
Report Issues
1. Check GitHub Issues 2. Search for error message 3. Include diagnostic information:
- Aspire version (
dotnet workload list) - OS and Docker version
- AppHost code (minimal repro)
- Full error message and stack trace
Resources
Troubleshooting Guides:
- Common Issues FAQ - Official troubleshooting guide
- Health Checks - Debugging health check failures
- Networking Issues - Service discovery problems
Community Support:
- GitHub Discussions - Q&A and community help
- Stack Overflow - Tagged questions
- Discord Server - Real-time community support
Most issues resolve by ensuring dependencies are properly referenced and health checks configured correctly.