
Working With Aspire
- 41 installs
- 2 repo stars
- Updated August 1, 2026
- mhagrelius/dotfiles
Helps with ai & agent building tasks.
About
working-with-aspire is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- working-with-aspire
- AI & Agent Building
- AI-coding skill
Working With Aspire by the numbers
- 41 all-time installs (skills.sh)
- Ranked #8,104 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mhagrelius/dotfiles --skill working-with-aspireAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 1, 2026 |
| Repository | mhagrelius/dotfiles ↗ |
What it does
Helps with ai & agent building tasks.
Files
Working with .NET Aspire 13
For AI Assistants: Use MCP Tools
CRITICAL: When helping users debug Aspire applications, use the Aspire MCP tools instead of curl, external HTTP calls, or suggesting manual dashboard inspection.
| Task | MCP Tool | NOT This |
|---|---|---|
| Check resource status | list_resources | ❌ curl to dashboard API |
| Get service logs | list_console_logs | ❌ curl, docker logs |
| View traces | list_traces | ❌ curl to OTLP endpoint |
| Find errors in traces | list_trace_structured_logs | ❌ manual dashboard search |
| List available AppHosts | list_apphosts | ❌ file system search |
If MCP is not configured, guide the user to run aspire mcp init in their AppHost directory.
Quick Reference
| Task | Reference File |
|---|---|
| AppHost patterns, resources, lifecycle | app-host.md |
| Azure integrations (databases, messaging, AI) | azure-integrations.md |
| Database integrations (Postgres, SQL, Mongo) | database-integrations.md |
| Caching (Redis, Valkey, Garnet) | caching-integrations.md |
| Messaging (Kafka, RabbitMQ, NATS) | messaging-integrations.md |
| Polyglot (Node.js, Python, Go, Rust, Java) | polyglot-integrations.md |
| Deployment, CLI, and aspire do pipelines | deployment-cli.md |
| Testing with Aspire | testing.md |
| Errors and troubleshooting | diagnostics.md |
| MCP integration for AI assistants | mcp-integration.md |
| VS Code extension | vs-code-extension.md |
| Certificate trust configuration | certificate-config.md |
| Migration from 9.x to 13.x | aspire-13-migration.md |
Common Mistakes
❌ Wrong: Using non-existent or deprecated APIs
| Wrong | Correct | Why |
|---|---|---|
AddPythonUvicornApp | AddUvicornApp | Method is just AddUvicornApp |
AddNpmApp | AddViteApp or AddJavaScriptApp | AddNpmApp removed in 13.0 |
AddPythonApp for FastAPI | AddUvicornApp | Use AddUvicornApp for ASGI (FastAPI, Starlette) |
Aspire.Hosting.NodeJs | Aspire.Hosting.JavaScript | Package renamed in 13.0 |
❌ Wrong: Putting secrets in appsettings.json
// WRONG - secrets exposed in source control
// appsettings.json: { "Parameters": { "api-key": "secret123" } }
// CORRECT - use user-secrets for development
// dotnet user-secrets set "Parameters:api-key" "secret123"Aspire 13 Breaking Changes
Critical: Migration from Aspire 9.x requires attention. See aspire-13-migration.md for full details.
JavaScript/Node.js Changes (13.0)
// BEFORE (9.x) - REMOVED IN 13.0
builder.AddNpmApp("frontend", "../app", scriptName: "dev", args: ["--no-open"])
// AFTER (13.0) - Use AddJavaScriptApp or AddViteApp
builder.AddJavaScriptApp("frontend", "../app")
.WithRunScript("dev")
.WithArgs("--no-open");
// For Vite/React specifically:
builder.AddViteApp("frontend", "../app")
.WithHttpEndpoint(env: "PORT");
// Package renamed: Aspire.Hosting.NodeJs → Aspire.Hosting.JavaScriptAzure Redis Changes (13.1)
// BEFORE (13.0) - DEPRECATED
builder.AddAzureRedisEnterprise("cache")
// AFTER (13.1)
builder.AddAzureManagedRedis("cache")Network Context Changes (13.0)
// BEFORE (9.x) - containerHostName parameter removed
await resource.ProcessArgumentValuesAsync(
executionContext, processValue, logger,
containerHostName: "localhost", cancellationToken);
// AFTER (13.0) - Use NetworkIdentifier
await resource.ProcessArgumentValuesAsync(
executionContext, processValue, logger, cancellationToken);
// Get endpoints with network context
var endpoint = api.GetEndpoint("http", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);Publishing API Changes (13.0)
// BEFORE (9.x)
public class MyPublisher : IDistributedApplicationPublisher { }
// AFTER (13.0) - Use aspire do pipelines instead
// IDistributedApplicationPublisher is deprecatedAspire 13.0+ New Features
Certificate Trust Automation
// Automatic - no configuration needed
var pythonApi = builder.AddUvicornApp("api", "./api", "main:app");
var nodeApi = builder.AddJavaScriptApp("frontend", "./frontend");
// Both automatically trust development certificatesMCP Integration for AI Assistants
# Initialize MCP for Claude Code, GitHub Copilot, etc.
aspire mcp initaspire do Pipeline System
aspire do build # Build container images
aspire do push # Push to registry
aspire do deploy # Full deployment
aspire do diagnostics # Show available stepsaspire init (Aspirify Existing Projects)
aspire init # Interactive setup
aspire init --single-file # Create .cs AppHost without .csprojSingle-File AppHost
// apphost.cs - no project file needed
#:package Aspire.Hosting@*
#:package Aspire.Hosting.Redis@*
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
builder.Build().Run();Common Patterns
FastAPI + React (Vite) Full Stack
// Required packages:
// dotnet add package Aspire.Hosting.Python
// dotnet add package Aspire.Hosting.JavaScript (NOT NodeJs!)
// dotnet add package Aspire.Hosting.PostgreSQL
var builder = DistributedApplication.CreateBuilder(args);
// Database
var db = builder.AddPostgres("db")
.AddDatabase("appdata")
.WithDataVolume();
// FastAPI backend - use AddUvicornApp (NOT AddPythonApp!)
var api = builder.AddUvicornApp("api", "../api", "main:app")
.WithHttpEndpoint(port: 8000, env: "PORT")
.WithReference(db)
.WaitFor(db);
// React frontend - use AddViteApp (NOT AddNpmApp!)
builder.AddViteApp("frontend", "../frontend")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.WithReference(api);
builder.Build().Run();Frontend reads API URL from env: process.env.services__api__http__0
Basic AppHost Structure
var builder = DistributedApplication.CreateBuilder(args);
// Database
var db = builder.AddPostgres("db")
.AddDatabase("appdata")
.WithDataVolume();
// API with database reference
var api = builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WaitFor(db);
// Frontend with API reference
builder.AddViteApp("frontend", "../frontend")
.WithHttpEndpoint(env: "PORT")
.WithReference(api);
builder.Build().Run();Service Discovery Pattern
// Producer exposes endpoint
var api = builder.AddProject<Projects.Api>("api")
.WithHttpEndpoint(port: 5000, name: "api");
// Consumer references it (gets services__api__api__0 env var)
builder.AddProject<Projects.Web>("web")
.WithReference(api);Health Checks and Wait
var db = builder.AddPostgres("db");
var cache = builder.AddRedis("cache");
builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WithReference(cache)
.WaitFor(db) // Wait for running
.WaitForHealthy(cache); // Wait for healthy (requires health check)When to Read Specific Files
Read `app-host.md` when:
- Setting up new AppHost project
- Understanding resource lifecycle events
- Configuring endpoints and networking
- Using parameters and secrets
Read `azure-integrations.md` when:
- Using Azure services (Cosmos, Service Bus, Storage)
- Running Azure emulators locally
- Using
RunAsEmulator()orRunAsContainer() - Customizing Bicep output
Read `polyglot-integrations.md` when:
- Adding Node.js, Python, Go, Rust, or Java apps
- Frontend frameworks (Vite, Angular, React)
- Container-based polyglot services
- Migrating from AddNpmApp to AddJavaScriptApp
Read `deployment-cli.md` when:
- Using
aspire deploy,aspire publish, oraspire do - Using
aspire initto Aspirify existing projects - Setting up CI/CD pipelines
- Understanding manifest format
Read `mcp-integration.md` when:
- Setting up AI assistant integration
- Configuring Claude Code, GitHub Copilot, or Cursor
- Excluding resources from MCP access
Read `vs-code-extension.md` when:
- Using Aspire in VS Code
- Debugging polyglot applications
- Creating new Aspire projects in VS Code
Read `certificate-config.md` when:
- Configuring HTTPS for polyglot apps
- Custom certificate authorities
- Certificate trust issues between services
Read `aspire-13-migration.md` when:
- Upgrading from Aspire 9.x
- Seeing deprecated API warnings
- Container networking issues after upgrade
Read `diagnostics.md` when:
- Seeing ASPIRE* compiler errors
- Debugging service discovery issues
- Troubleshooting container startup
AppHost Reference
Core Concepts
The AppHost is the orchestration entry point for Aspire applications. It defines resources, their relationships, and how they communicate.
Basic Structure
var builder = DistributedApplication.CreateBuilder(args);
// Define resources
var db = builder.AddPostgres("db").AddDatabase("appdata");
var api = builder.AddProject<Projects.Api>("api");
var frontend = builder.AddViteApp("frontend", "../frontend");
// Wire dependencies
api.WithReference(db).WaitFor(db);
frontend.WithReference(api);
builder.Build().Run();Resource Types
| Method | Resource Type | Description |
|---|---|---|
AddProject<T>() | .NET Project | References a .NET project |
AddContainer() | Container | Generic container from image |
AddDockerfile() | Dockerfile | Build from Dockerfile (see Dockerfiles section) |
AddExecutable() | Executable | External executable (see Executables section) |
AddParameter() | Parameter | External configuration value |
AddConnectionString() | Connection | External connection string |
API Patterns
AddX Pattern (Create Resources)
// Returns IResourceBuilder<TResource>
var redis = builder.AddRedis("cache");
var postgres = builder.AddPostgres("db").AddDatabase("mydb");
var api = builder.AddProject<Projects.Api>("api");WithX Pattern (Configure Resources)
builder.AddProject<Projects.Api>("api")
.WithReference(db) // Inject connection string
.WithEnvironment("KEY", val) // Add env var
.WithEndpoint(port: 8080) // Expose endpoint
.WithReplicas(3) // Scale replicas
.WaitFor(db); // Startup orderingDependency Wiring
WithReference - Connection Injection
var db = builder.AddPostgres("db").AddDatabase("appdata");
var cache = builder.AddRedis("cache");
// Injects ConnectionStrings__appdata and ConnectionStrings__cache
builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WithReference(cache);WaitFor - Startup Ordering
// WaitFor: waits for resource to be running
api.WaitFor(db);
// WaitForHealthy: waits for health check to pass (requires health check)
api.WaitForHealthy(cache);
// WaitForCompletion: waits for resource to complete (jobs)
api.WaitForCompletion(migration);HTTP Health Probes (Kubernetes-Style)
Configure startup, readiness, and liveness probes for Kubernetes deployments:
// Single probe
var api = builder.AddProject<Projects.Api>("api")
.WithHttpProbe(ProbeType.Readiness, "/health/ready");
// Multiple probes with custom settings
var service = builder.AddProject<Projects.Service>("service")
.WithHttpProbe(ProbeType.Startup, "/health/startup",
initialDelaySeconds: 15, failureThreshold: 10)
.WithHttpProbe(ProbeType.Readiness, "/health/ready",
periodSeconds: 5, timeoutSeconds: 3)
.WithHttpProbe(ProbeType.Liveness, "/health/live",
periodSeconds: 30, failureThreshold: 3);| Probe Type | Purpose | When It Runs |
|---|---|---|
ProbeType.Startup | Detect slow-starting containers | During startup only |
ProbeType.Readiness | Control traffic routing | Continuously after startup |
ProbeType.Liveness | Detect hung processes | Continuously after startup |
Target specific endpoint:
// Probe a named endpoint (e.g., management port)
var api = builder.AddProject<Projects.Api>("api")
.WithHttpEndpoint(8080, name: "management")
.WithHttpProbe(ProbeType.Readiness, "/actuator/health",
endpointName: "management");Note: WithHttpProbe is in preview. Suppress warnings with:```csharp
#pragma warning disable ASPIREPROBES001
.WithHttpProbe(ProbeType.Readiness, "/health/ready")
#pragma warning restore ASPIREPROBES001
```
Environment Variables
Static Values
.WithEnvironment("DEBUG", "true")
.WithEnvironment("LOG_LEVEL", "info")Dynamic Values (Callback)
.WithEnvironment(context =>
{
context.EnvironmentVariables["COMPUTED"] = ComputeValue();
})From Endpoints
var api = builder.AddProject<Projects.Api>("api");
builder.AddProject<Projects.Web>("web")
.WithEnvironment("API_URL", api.GetEndpoint("http"));From Parameters
var secret = builder.AddParameter("api-key", secret: true);
builder.AddProject<Projects.Api>("api")
.WithEnvironment("API_KEY", secret);Endpoints and Networking
Define Endpoints
// HTTP endpoint with specific port
.WithHttpEndpoint(port: 5000)
// HTTPS endpoint
.WithHttpsEndpoint(port: 5001)
// Named endpoint
.WithHttpEndpoint(port: 8080, name: "admin")
// Container port mapping (host:container)
.WithHttpEndpoint(port: 8000, targetPort: 8080)
// Environment variable for port
.WithHttpEndpoint(env: "PORT")Endpoint Configuration
.WithEndpoint(endpointName: "grpc", callback: endpoint =>
{
endpoint.Port = 5000;
endpoint.UriScheme = "http";
endpoint.Transport = "http2";
})External Endpoints (Dashboard Access)
.WithExternalHttpEndpoints() // Expose HTTP endpoints externallyCustom URLs
// Add custom URL to dashboard
api.WithUrl("/swagger", "Swagger UI");
api.WithUrlForEndpoint("https", url => {
url.DisplayText = "Admin Portal";
url.Url = "/admin";
});Parameters and Secrets
Define Parameters
// Regular parameter (non-sensitive)
var rows = builder.AddParameter("insertion-rows");
// Secret parameter (sensitive)
var apiKey = builder.AddParameter("api-key", secret: true);Where to Store Values
Regular parameters → appsettings.json:
{
"Parameters": {
"insertion-rows": "100"
}
}Secret parameters → User Secrets (NEVER appsettings.json):
# Initialize user secrets (once per project)
dotnet user-secrets init
# Set the secret value
dotnet user-secrets set "Parameters:api-key" "my-secret-key"WARNING: Never put secret values inappsettings.json. This file is committed to source control. Usedotnet user-secretsfor development and Azure Key Vault or environment variables for production.
Pass Parameters to Resources
var secret = builder.AddParameter("api-key", secret: true);
var rows = builder.AddParameter("max-rows");
builder.AddUvicornApp("api", "../api", "main:app")
.WithEnvironment("API_KEY", secret) // Secret as env var
.WithEnvironment("MAX_ROWS", rows); // Regular param as env varConnection String Parameters
// External connection string from config
var redis = builder.AddConnectionString("redis");
// Composed connection string
var secret = builder.AddParameter("key", secret: true);
var conn = builder.AddConnectionString("api",
ReferenceExpression.Create($"Endpoint=https://api.com;Key={secret}"));Volumes and Data Persistence
Data Volumes (Recommended)
// Auto-named volume
builder.AddPostgres("db").WithDataVolume();
// Custom volume
builder.AddSqlServer("sql")
.WithVolume(name: "sql-data", target: "/var/opt/mssql");Bind Mounts
builder.AddPostgres("db")
.WithDataBindMount(source: @"C:\Data\postgres");Persistent Passwords
# Set persistent password in user secrets
dotnet user-secrets set Parameters:db-password "my-password"var password = builder.AddParameter("db-password", secret: true);
builder.AddPostgres("db", password: password).WithDataVolume();Lifecycle Events
AppHost Events (Order)
1. BeforeStartEvent - Before AppHost starts 2. ResourceEndpointsAllocatedEvent - Per resource, after endpoints allocated 3. AfterResourcesCreatedEvent - After all resources created
Subscribe to Events
builder.Eventing.Subscribe<BeforeStartEvent>((@event, ct) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Starting...");
return Task.CompletedTask;
});Resource Events (Order)
1. InitializeResourceEvent - Resource initialization 2. ResourceEndpointsAllocatedEvent - Endpoints allocated 3. ConnectionStringAvailableEvent - Connection string ready 4. BeforeResourceStartedEvent - Before resource starts 5. ResourceReadyEvent - Resource is ready
Subscribe to Resource Events
var cache = builder.AddRedis("cache");
cache.OnResourceReady((resource, @event, ct) =>
{
// Cache is ready
return Task.CompletedTask;
});
cache.OnBeforeResourceStarted((resource, @event, ct) =>
{
// About to start
return Task.CompletedTask;
});Lifecycle Hook Interface
builder.Services.AddLifecycleHook<MyLifecycleHook>();
class MyLifecycleHook : IDistributedApplicationLifecycleHook
{
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken ct)
{
// Before start
return Task.CompletedTask;
}
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken ct)
{
// After endpoints allocated
return Task.CompletedTask;
}
public Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken ct)
{
// After resources created
return Task.CompletedTask;
}
}Container Lifetime
Persistent Containers
// Container persists across AppHost restarts
builder.AddRedis("cache")
.WithLifetime(ContainerLifetime.Persistent);Execution Context
Check Run vs Publish Mode
if (builder.ExecutionContext.IsRunMode)
{
// Local development
}
if (builder.ExecutionContext.IsPublishMode)
{
// Publishing/deployment
}Common Patterns
Full Stack Example
var builder = DistributedApplication.CreateBuilder(args);
// Infrastructure
var db = builder.AddPostgres("db")
.WithDataVolume()
.AddDatabase("appdata");
var cache = builder.AddRedis("cache")
.WithLifetime(ContainerLifetime.Persistent);
// Backend
var api = builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WithReference(cache)
.WaitFor(db)
.WaitFor(cache);
// Frontend
builder.AddViteApp("frontend", "../frontend")
.WithHttpEndpoint(env: "PORT")
.WithReference(api)
.WaitFor(api);
builder.Build().Run();Microservices Pattern
var messaging = builder.AddRabbitMQ("messaging");
var orderService = builder.AddProject<Projects.Orders>("orders")
.WithReference(messaging);
var inventoryService = builder.AddProject<Projects.Inventory>("inventory")
.WithReference(messaging);
var gateway = builder.AddProject<Projects.Gateway>("gateway")
.WithReference(orderService)
.WithReference(inventoryService);Executables (AddExecutable)
Run external executables (Python scripts, Go binaries, etc.) as Aspire resources:
// Basic executable
builder.AddExecutable("processor", "python", workingDirectory: "../scripts", "process_data.py");
// With arguments
builder.AddExecutable("worker", "python", "../scripts", "worker.py", "--mode", "production");
// With dependencies
var redis = builder.AddRedis("cache");
builder.AddExecutable("processor", "python", "../scripts", "process.py")
.WithReference(redis) // Injects ConnectionStrings__cache
.WithEnvironment("LOG_LEVEL", "debug");
// With HTTP endpoint (if it exposes a server)
builder.AddExecutable("api", "python", "../api", "server.py")
.WithHttpEndpoint(port: 8000, env: "PORT")
.WithReference(db);Dockerfiles
AddDockerfile - New Container from Dockerfile
// Build container from Dockerfile in directory
builder.AddDockerfile("goservice", "./go-service");
// Specify custom Dockerfile name
builder.AddDockerfile("service", "./path", "Dockerfile.custom");
// With dependencies and endpoints
var db = builder.AddPostgres("db").AddDatabase("appdata");
builder.AddDockerfile("goservice", "./go-service")
.WithReference(db)
.WithHttpEndpoint(port: 8080);WithDockerfile - Customize Existing Resource
Use a custom Dockerfile while keeping resource-specific extension methods:
// Use custom Postgres image with PostGIS extensions
builder.AddPostgres("db")
.WithDockerfile("./postgres-postgis") // Path to directory with Dockerfile
.AddDatabase("geodata");
// Still has all PostgreSQL methods available
builder.AddPostgres("db")
.WithDockerfile("./custom-postgres")
.WithDataVolume()
.WithPgAdmin();Key difference:
AddDockerfile()- Creates new generic container resourceWithDockerfile()- Customizes image for existing typed resource (keeps extension methods)
Bind Mounts
Mount local files or directories into containers:
// Mount single file
builder.AddContainer("nginx", "nginx:latest")
.WithBindMount("./nginx.conf", "/etc/nginx/nginx.conf");
// Mount directory
builder.AddContainer("nginx", "nginx:latest")
.WithBindMount("./html", "/usr/share/nginx/html");
// Read-only mount
builder.AddContainer("app", "myapp:latest")
.WithBindMount("./config", "/app/config", isReadOnly: true);
// Multiple mounts
builder.AddContainer("nginx", "nginx:latest")
.WithBindMount("./nginx.conf", "/etc/nginx/nginx.conf", isReadOnly: true)
.WithBindMount("./html", "/usr/share/nginx/html", isReadOnly: true)
.WithBindMount("./logs", "/var/log/nginx"); // Writable for logsNote: Bind mounts use host paths. For named Docker volumes, use WithVolume() instead.
Replicas and Proxy Behavior
WithReplicas
// Run 3 instances of the API
builder.AddProject<Projects.Api>("api")
.WithReplicas(3);How it works:
- Aspire creates a proxy on the specified port
- Each replica gets a randomly assigned port
- Other services using
WithReference(api)get the proxy URL, not individual replicas - Proxy handles load balancing between replicas
- Dashboard shows replicas nested under the parent resource
Endpoint Proxy Behavior (IsProxied)
By default, Aspire proxies endpoints. For executables that manage their own ports:
// Problem: App listens on 3000, but not accessible on that port
builder.AddExecutable("app", "node", "../app", "server.js")
.WithHttpEndpoint(port: 3000); // Port 3000 is for PROXY, not app!
// Solution 1: Disable proxy
builder.AddExecutable("app", "node", "../app", "server.js")
.WithEndpoint("http", endpoint =>
{
endpoint.Port = 3000;
endpoint.IsProxied = false; // App manages its own port
});
// Solution 2: Use environment variable for dynamic port
builder.AddExecutable("app", "node", "../app", "server.js")
.WithHttpEndpoint(env: "PORT"); // App reads PORT env varWhen to disable proxy:
- External executables with hardcoded ports
- Apps that don't read port from environment
- When you need the exact port the app binds to
WaitForCompletion (Job/Migration Pattern)
For containers that should run once and exit (migrations, seed scripts, init jobs):
// Migration container runs EF Core migrations
var migration = builder.AddDockerfile("migration", "./migrations")
.WithReference(db);
// API waits for migration to COMPLETE (exit successfully), not just start
var api = builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WaitForCompletion(migration); // Not WaitFor!Key difference:
WaitFor(resource)- Waits for resource to reach Running stateWaitForCompletion(resource)- Waits for resource to exit successfully
Use WaitForCompletion for:
- Database migrations
- Seed data scripts
- Init containers
- One-time setup jobs
Custom Dashboard Commands (WithCommand)
Add custom commands to resources that appear in the Aspire dashboard:
var redis = builder.AddRedis("cache")
.WithCommand("clear-cache", "Clear All Keys", async context =>
{
var connectionString = await context.Resource.GetConnectionStringAsync();
// Use connection string to clear cache
using var redis = ConnectionMultiplexer.Connect(connectionString!);
var server = redis.GetServer(redis.GetEndPoints().First());
await server.FlushAllDatabasesAsync();
return CommandResults.Success("Cache cleared");
});Commands appear in the dashboard resource actions menu.
Custom HTTP Commands (WithHttpCommand)
For commands that need to call HTTP endpoints on resources (like cache invalidation), use WithHttpCommand:
var apiCacheInvalidationKey = builder.AddParameter("ApiCacheInvalidationKey", secret: true);
var api = builder.AddProject<Projects.Api>("api")
.WithEnvironment("ApiCacheInvalidationKey", apiCacheInvalidationKey)
.WithHttpCommand(
path: "/cache/invalidate",
displayName: "Invalidate cache",
commandOptions: new HttpCommandOptions()
{
Description = "Invalidates the API cache. All cached values are cleared!",
PrepareRequest = (context) =>
{
var key = apiCacheInvalidationKey.Resource.GetValueAsync(context.CancellationToken);
context.Request.Headers.Add("X-CacheInvalidation-Key", $"Key: {key}");
return Task.CompletedTask;
},
IconName = "DocumentLightning",
IsHighlighted = true
});Key difference:
WithCommand()- Execute custom code in the AppHost processWithHttpCommand()- Send HTTP request to resource's endpoint (requires endpoint to handle it)
HttpCommandOptions properties:
| Property | Description |
|---|---|
Description | Shown in dashboard UI |
PrepareRequest | Callback to configure request headers (e.g., auth tokens) |
IconName | Fluent UI icon name |
IsHighlighted | Show prominently in UI |
Security: Use PrepareRequest to add authentication headers with shared secrets from parameters. The endpoint should validate these to prevent unauthorized access.
Container Runtime Arguments
Pass Docker-specific flags (not container entrypoint args):
// Resource limits
builder.AddContainer("app", "myapp:latest")
.WithContainerRuntimeArgs("--memory=512m", "--cpus=2");
// Security options
builder.AddContainer("app", "myapp:latest")
.WithContainerRuntimeArgs("--cap-drop=ALL", "--read-only");
// Network mode
builder.AddContainer("app", "myapp:latest")
.WithContainerRuntimeArgs("--network=host");Key difference:
WithArgs()- Arguments passed to the container's entrypoint/commandWithContainerRuntimeArgs()- Arguments passed to Docker runtime (docker run)
Aspire 13 Migration Guide
Overview
Aspire 13.0 is a major release with significant changes. The framework has been rebranded from ".NET Aspire" to simply "Aspire" to reflect its polyglot nature.
Requirements: .NET 10 SDK or later
Upgrade Methods
Using Aspire CLI (Recommended)
# Update CLI first
aspire update --self
# Update project packages
aspire updateFrom Aspire 8.x
If upgrading from Aspire 8.x, first upgrade to 9.x, then to 13.0:
# Remove legacy workload
dotnet workload uninstall aspireManual Upgrade
Update your AppHost project file:
<!-- Before (9.x) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.*" />
</ItemGroup>
</Project>
<!-- After (13.0) -->
<Project Sdk="Aspire.AppHost.Sdk/13.0.0">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>Breaking Changes in 13.0
Network Context Changes
`containerHostName` parameter removed:
// Before (9.x)
await resource.ProcessArgumentValuesAsync(
executionContext, processValue, logger,
containerHostName: "localhost", cancellationToken);
// After (13.0)
await resource.ProcessArgumentValuesAsync(
executionContext, processValue, logger, cancellationToken);Use `NetworkIdentifier` instead:
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject<Projects.Api>("api");
// Get endpoint with specific network context
var localhostEndpoint = api.GetEndpoint("http", KnownNetworkIdentifiers.LocalhostNetwork);
var containerEndpoint = api.GetEndpoint("http", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);`AllocatedEndpoint` constructor changed:
// Before (9.x)
var endpoint = new AllocatedEndpoint(
endpointAnnotation, "http", 8080);
// After (13.0)
var endpoint = new AllocatedEndpoint(
endpointAnnotation, "http", 8080,
networkIdentifier: KnownNetworkIdentifiers.LocalhostNetwork);JavaScript/Node.js API Changes
`AddNpmApp` removed - use `AddJavaScriptApp`:
// Before (9.x) - REMOVED
builder.AddNpmApp("frontend", "../app", scriptName: "dev", args: ["--no-open"]);
// After (13.0) - Option 1: WithArgs
builder.AddJavaScriptApp("frontend", "../app")
.WithRunScript("dev")
.WithArgs("--no-open");
// After (13.0) - Option 2: package.json script
// Define in package.json: "dev:custom": "vite --no-open"
builder.AddJavaScriptApp("frontend", "../app")
.WithRunScript("dev:custom");Package renamed:
// Before (9.x)
// Package: Aspire.Hosting.NodeJs
// After (13.0)
// Package: Aspire.Hosting.JavaScriptAzure Credential Behavior Change
`DefaultAzureCredential` now defaults to `ManagedIdentityCredential` on Azure Container Apps and App Service.
If you rely on other credential types in these environments, explicitly configure them:
builder.Services.AddAzureClients(clients =>
{
clients.UseCredential(new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeManagedIdentityCredential = true // If needed
}));
});Publishing API Changes
`IDistributedApplicationPublisher` deprecated:
// Before (9.x)
public class MyPublisher : IDistributedApplicationPublisher { }
builder.Services.AddKeyedSingleton<IDistributedApplicationPublisher, MyPublisher>("my");
// After (13.0) - Use PipelineStep
public class MyDeployStep : PipelineStep { }Lifecycle Hook Changes
// Before (9.x)
builder.Services.AddLifecycleHook<MyHook>();
builder.Services.TryAddLifecycleHook<MyHook>();
// After (13.0)
builder.Services.AddEventingSubscriber<MySubscriber>();
builder.Services.TryAddEventingSubscriber<MySubscriber>();Breaking Changes in 13.1
Azure Redis API Rename
// Before (13.0) - DEPRECATED
var redis = builder.AddAzureRedisEnterprise("cache");
// After (13.1)
var redis = builder.AddAzureManagedRedis("cache");Note: AddAzureRedis is now obsolete. Use AddAzureManagedRedis for new projects.
Migration Checklist
From 9.x to 13.0
- [ ] Install .NET 10 SDK
- [ ] Update AppHost SDK:
Aspire.AppHost.Sdk/13.0.0 - [ ] Update
TargetFrameworktonet10.0 - [ ] Replace
AddNpmAppwithAddJavaScriptApp - [ ] Update
Aspire.Hosting.NodeJstoAspire.Hosting.JavaScript - [ ] Replace
containerHostNamewithNetworkIdentifier - [ ] Update
AllocatedEndpointconstructors - [ ] Replace
IDistributedApplicationPublisherwithPipelineStep - [ ] Replace lifecycle hooks with eventing subscribers
- [ ] Test Azure credential behavior in cloud environments
From 13.0 to 13.1
- [ ] Run
aspire update --self - [ ] Run
aspire updatein project directory - [ ] Replace
AddAzureRedisEnterprisewithAddAzureManagedRedis - [ ] Run
aspire mcp initto set up AI coding agent support
New Features to Adopt
Polyglot Improvements
// Unified JavaScript method
var frontend = builder.AddJavaScriptApp("frontend", "./frontend")
.WithYarn() // or WithPnpm()
.WithRunScript("dev")
.WithBuildScript("build");Certificate Trust Automation
// Automatic certificate trust (no configuration needed)
var pythonApi = builder.AddUvicornApp("api", "./api", "main:app");
var nodeApi = builder.AddJavaScriptApp("frontend", "./frontend");
var container = builder.AddContainer("service", "myimage");
// All automatically trust development certificatesMCP Integration
# Set up AI assistant integration
aspire mcp initPipeline System
# Use aspire do for granular control
aspire do build
aspire do push
aspire do deploy-apiserviceTroubleshooting Migration
"Package not found" errors
Ensure you've updated package names:
Aspire.Hosting.NodeJs→Aspire.Hosting.JavaScript
"Method not found" errors
Check for removed APIs:
AddNpmApp→AddJavaScriptAppcontainerHostNameparameter →NetworkIdentifier
Container networking issues
If containers can't communicate:
// Use network identifiers explicitly
var endpoint = api.GetEndpoint("http", KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
builder.AddProject<Projects.Worker>("worker")
.WithEnvironment("API_URL", endpoint);Azure authentication failures
If Azure resources fail in cloud:
// Check DefaultAzureCredential behavior change
// May need to explicitly configure credential optionsResources
Azure Integrations Reference
Overview
Azure integrations in Aspire provide two main capabilities: 1. Hosting integrations: Model Azure resources in AppHost for local dev and deployment 2. Client integrations: Configure .NET clients with proper defaults
Local Development Patterns
Emulators vs Containers vs Real Azure
| Pattern | API | Use Case |
|---|---|---|
| Real Azure | AddAzure*() | Test with actual Azure services |
| Emulator | .RunAsEmulator() | Official Azure emulators |
| Container | .RunAsContainer() | Open-source container alternatives |
Emulator Support
// Cosmos DB Emulator
builder.AddAzureCosmosDB("cosmos").RunAsEmulator();
// Service Bus Emulator
builder.AddAzureServiceBus("messaging").RunAsEmulator();
// Event Hubs Emulator
builder.AddAzureEventHubs("events").RunAsEmulator();
// Storage (Azurite)
builder.AddAzureStorage("storage").RunAsEmulator();
// App Configuration Emulator
builder.AddAzureAppConfiguration("config").RunAsEmulator();
// SignalR Emulator
builder.AddAzureSignalR("signalr").RunAsEmulator();
// AI Foundry Local
builder.AddAzureAIFoundry("ai").RunAsFoundryLocal();Container Substitution
// Azure Cache for Redis → Local Redis container
builder.AddAzureRedis("cache").RunAsContainer();
// Azure PostgreSQL → Local PostgreSQL container
builder.AddAzurePostgresFlexibleServer("db").RunAsContainer();
// Azure SQL → Local SQL Server container
builder.AddAzureSqlServer("sql").RunAsContainer();Run vs Publish Behavior
| API | Local (Run) | Deploy (Publish) |
|---|---|---|
AddAzureRedis("r").RunAsContainer() | Redis container | Azure Cache for Redis |
AddRedis("r") | Redis container | Container App with Redis |
AddAzurePostgresFlexibleServer("p").RunAsContainer() | PostgreSQL container | Azure PostgreSQL Flexible |
AddPostgres("p") | PostgreSQL container | Container App with PostgreSQL |
Existing Resources
Use Existing in All Modes
var nameParam = builder.AddParameter("cosmosName");
var rgParam = builder.AddParameter("resourceGroup");
builder.AddAzureCosmosDB("cosmos")
.AsExisting(nameParam, rgParam);Use Existing in Run Mode Only
builder.AddAzureServiceBus("messaging")
.RunAsExisting(nameParam, rgParam);Use Existing in Publish Mode Only
builder.AddAzureServiceBus("messaging")
.PublishAsExisting(nameParam, rgParam);Common Azure Services
Azure Cosmos DB
// Package: Aspire.Hosting.Azure.CosmosDB
// Provision new
var cosmos = builder.AddAzureCosmosDB("cosmos");
var db = cosmos.AddCosmosDatabase("mydb");
var container = db.AddCosmosContainer("items", "/partitionKey");
// With emulator
var cosmos = builder.AddAzureCosmosDB("cosmos")
.RunAsEmulator();
// Reference in project
builder.AddProject<Projects.Api>("api")
.WithReference(cosmos);Client Integration:
// Package: Aspire.Microsoft.Azure.Cosmos
builder.AddAzureCosmosClient("cosmos");Azure Service Bus
// Package: Aspire.Hosting.Azure.ServiceBus
var serviceBus = builder.AddAzureServiceBus("messaging");
// Add queue
serviceBus.AddServiceBusQueue("orders");
// Add topic with subscription
var topic = serviceBus.AddServiceBusTopic("notifications");
topic.AddServiceBusSubscription("email-handler");
// With emulator
builder.AddAzureServiceBus("messaging")
.RunAsEmulator();Client Integration:
// Package: Aspire.Azure.Messaging.ServiceBus
builder.AddAzureServiceBusClient("messaging");Azure Storage
// Package: Aspire.Hosting.Azure.Storage
var storage = builder.AddAzureStorage("storage");
var blobs = storage.AddBlobs("blobs");
var queues = storage.AddQueues("queues");
var tables = storage.AddTables("tables");
// With Azurite emulator
builder.AddAzureStorage("storage")
.RunAsEmulator();Client Integrations:
// Package: Aspire.Azure.Storage.Blobs
builder.AddAzureBlobClient("blobs");
// Package: Aspire.Azure.Storage.Queues
builder.AddAzureQueueClient("queues");
// Package: Aspire.Azure.Data.Tables
builder.AddAzureTableClient("tables");Azure Event Hubs
// Package: Aspire.Hosting.Azure.EventHubs
var eventHubs = builder.AddAzureEventHubs("events");
eventHubs.AddEventHub("telemetry");
// With emulator
builder.AddAzureEventHubs("events")
.RunAsEmulator();Client Integration:
// Package: Aspire.Azure.Messaging.EventHubs
builder.AddAzureEventHubProducerClient("events", "telemetry");
builder.AddAzureEventHubConsumerClient("events", "telemetry");Azure Key Vault
// Package: Aspire.Hosting.Azure.KeyVault
var keyVault = builder.AddAzureKeyVault("secrets");
builder.AddProject<Projects.Api>("api")
.WithReference(keyVault);Client Integration:
// Package: Aspire.Azure.Security.KeyVault
builder.AddAzureKeyVaultClient("secrets");Azure PostgreSQL Flexible Server
// Package: Aspire.Hosting.Azure.PostgreSQL
var postgres = builder.AddAzurePostgresFlexibleServer("pg")
.AddDatabase("mydb");
// Run as container locally
builder.AddAzurePostgresFlexibleServer("pg")
.RunAsContainer()
.AddDatabase("mydb");Client Integration:
// Package: Aspire.Azure.Npgsql
builder.AddAzureNpgsqlDataSource("mydb");Azure SQL Database
// Package: Aspire.Hosting.Azure.Sql
var sql = builder.AddAzureSqlServer("sql")
.AddDatabase("mydb");
// Run as container locally
builder.AddAzureSqlServer("sql")
.RunAsContainer()
.AddDatabase("mydb");Client Integration:
// Package: Aspire.Azure.Data.SqlClient
builder.AddAzureSqlClient("mydb");Azure Managed Redis (13.1+)
// Package: Aspire.Hosting.Azure.Redis
// New API (13.1+)
var redis = builder.AddAzureManagedRedis("cache");
// Run as container locally
builder.AddAzureManagedRedis("cache")
.RunAsContainer();Migration from older APIs:
// Before (13.0) - DEPRECATED
var redis = builder.AddAzureRedisEnterprise("cache");
// Before - OBSOLETE
var redis = builder.AddAzureRedis("cache");
// After (13.1+)
var redis = builder.AddAzureManagedRedis("cache");Client Integration:
// Package: Aspire.Azure.StackExchange.Redis
builder.AddAzureRedisClient("cache");Azure OpenAI
// Package: Aspire.Hosting.Azure.CognitiveServices
var openai = builder.AddAzureOpenAI("openai");
openai.AddDeployment(new("gpt-4o", "gpt-4o", "2024-08-06"));
builder.AddProject<Projects.Api>("api")
.WithReference(openai);Client Integration:
// Package: Aspire.Azure.AI.OpenAI
builder.AddAzureOpenAIClient("openai");Azure App Configuration
// Package: Aspire.Hosting.Azure.AppConfiguration
var config = builder.AddAzureAppConfiguration("config");
// With emulator
builder.AddAzureAppConfiguration("config")
.RunAsEmulator();Client Integration:
// Package: Aspire.Azure.AppConfiguration
builder.AddAzureAppConfigurationClient("config");Azure SignalR
// Package: Aspire.Hosting.Azure.SignalR
var signalr = builder.AddAzureSignalR("signalr");
// With emulator
builder.AddAzureSignalR("signalr")
.RunAsEmulator();Azure Functions
// Package: Aspire.Hosting.Azure.Functions
builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions")
.WithReference(storage)
.WithReference(serviceBus);Customizing Azure Resources
Configure Infrastructure
builder.AddAzureStorage("storage")
.ConfigureInfrastructure(infra =>
{
var storageAccount = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
storageAccount.Sku = new StorageSku(StorageSkuName.StandardGrs);
storageAccount.Tags.Add("Environment", "Production");
});Role Assignments
var storage = builder.AddAzureStorage("storage");
builder.AddProject<Projects.Api>("api")
.WithRoleAssignments(storage, StorageBuiltInRole.StorageBlobDataContributor);Container App Environment
// Configure Azure Container Apps deployment
var acr = builder.AddAzureContainerRegistry("registry");
builder.AddAzureContainerAppEnvironment("env")
.WithAzureContainerRegistry(acr);
builder.AddProject<Projects.Api>("api")
.PublishAsAzureContainerApp();Local Provisioning Configuration
Required in appsettings.json for real Azure resources:
{
"Azure": {
"SubscriptionId": "your-subscription-id",
"Location": "eastus",
"ResourceGroup": "rg-myapp",
"AllowResourceGroupCreation": true
}
}Or via environment variables:
AZURE_SUBSCRIPTION_ID=...
AZURE_LOCATION=eastus
AZURE_RESOURCE_GROUP=rg-myappTroubleshooting
Authentication Failures
az login
az account set --subscription "your-subscription-id"Enable Debug Logging
{
"Logging": {
"LogLevel": {
"Aspire.Hosting.Azure": "Debug"
}
}
}Common Issues
- Missing configuration: Ensure
SubscriptionId,Location,ResourceGroupare set - Insufficient permissions: Need Contributor role on resource group
- Emulator not starting: Check Docker is running and port conflicts
Caching Integrations Reference
Quick Reference
| Cache | Hosting Package | Client Package | Add Method |
|---|---|---|---|
| Redis | Aspire.Hosting.Redis | Aspire.StackExchange.Redis | AddRedis() |
| Valkey | Aspire.Hosting.Valkey | Aspire.StackExchange.Redis | AddValkey() |
| Garnet | Aspire.Hosting.Garnet | Aspire.StackExchange.Redis | AddGarnet() |
Redis
Hosting Integration
// Package: Aspire.Hosting.Redis
var redis = builder.AddRedis("cache")
.WithDataVolume() // Persist data
.WithLifetime(ContainerLifetime.Persistent) // Keep across restarts
.WithRedisCommander(); // Add Redis Commander UI
builder.AddProject<Projects.Api>("api")
.WithReference(redis)
.WaitFor(redis);Client Integration
// Package: Aspire.StackExchange.Redis
builder.AddRedisClient("cache");Use in Services
public class CacheService(IConnectionMultiplexer redis)
{
public async Task<string?> GetAsync(string key)
{
var db = redis.GetDatabase();
return await db.StringGetAsync(key);
}
public async Task SetAsync(string key, string value, TimeSpan? expiry = null)
{
var db = redis.GetDatabase();
await db.StringSetAsync(key, value, expiry);
}
}Distributed Cache
// Package: Aspire.StackExchange.Redis.DistributedCaching
builder.AddRedisDistributedCache("cache");
// Use via IDistributedCache
public class MyService(IDistributedCache cache)
{
public async Task<byte[]?> GetAsync(string key)
{
return await cache.GetAsync(key);
}
}Output Caching
// Package: Aspire.StackExchange.Redis.OutputCaching
builder.AddRedisOutputCache("cache");
// Configure in Program.cs
app.UseOutputCache();
// Use on endpoints
app.MapGet("/data", () => GetData())
.CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(5)));Redis Configuration
{
"Aspire": {
"StackExchange": {
"Redis": {
"DisableHealthChecks": false,
"DisableTracing": false
}
}
}
}Valkey (Redis Fork)
Hosting Integration
// Package: Aspire.Hosting.Valkey
var valkey = builder.AddValkey("cache")
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);
builder.AddProject<Projects.Api>("api")
.WithReference(valkey);Client Integration
// Same as Redis - uses StackExchange.Redis
builder.AddRedisClient("cache");Garnet (Microsoft Redis Alternative)
Hosting Integration
// Package: Aspire.Hosting.Garnet
var garnet = builder.AddGarnet("cache")
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);
builder.AddProject<Projects.Api>("api")
.WithReference(garnet);Client Integration
// Same as Redis - uses StackExchange.Redis
builder.AddRedisClient("cache");Azure Cache for Redis
Hosting Integration
// Package: Aspire.Hosting.Azure.Redis
var redis = builder.AddAzureRedis("cache");
// Run as container locally
builder.AddAzureRedis("cache").RunAsContainer();Client Integration
// Package: Aspire.Azure.StackExchange.Redis
builder.AddAzureRedisClient("cache");Common Patterns
Persistent Container
// Keep Redis running across AppHost restarts
builder.AddRedis("cache")
.WithLifetime(ContainerLifetime.Persistent);Redis Commander UI
// Add web-based Redis management
builder.AddRedis("cache").WithRedisCommander();Redis Insight
// Add RedisInsight for visualization
builder.AddRedis("cache").WithRedisInsight();Data Persistence
// Docker volume (recommended)
builder.AddRedis("cache").WithDataVolume();
// Bind mount
builder.AddRedis("cache")
.WithDataBindMount(source: "./data/redis");Custom Configuration
builder.AddRedis("cache")
.WithEndpoint(port: 6380, name: "redis")
.WithEnvironment("REDIS_ARGS", "--maxmemory 256mb");Caching Patterns
Cache-Aside Pattern
public class ProductService(IConnectionMultiplexer redis, IProductRepository repo)
{
public async Task<Product?> GetProductAsync(int id)
{
var db = redis.GetDatabase();
var key = $"product:{id}";
// Try cache first
var cached = await db.StringGetAsync(key);
if (cached.HasValue)
return JsonSerializer.Deserialize<Product>(cached!);
// Load from database
var product = await repo.GetAsync(id);
if (product != null)
{
await db.StringSetAsync(key,
JsonSerializer.Serialize(product),
TimeSpan.FromMinutes(10));
}
return product;
}
}Distributed Lock
public class LockService(IConnectionMultiplexer redis)
{
public async Task<bool> AcquireLockAsync(string resource, TimeSpan expiry)
{
var db = redis.GetDatabase();
return await db.StringSetAsync(
$"lock:{resource}",
Environment.MachineName,
expiry,
When.NotExists);
}
public async Task ReleaseLockAsync(string resource)
{
var db = redis.GetDatabase();
await db.KeyDeleteAsync($"lock:{resource}");
}
}Environment Variables
When using WithReference(cache):
| Variable | Value |
|---|---|
ConnectionStrings__cache | localhost:6379 |
CACHE_HOST | localhost |
CACHE_PORT | 6379 |
Certificate Configuration Reference
Overview
Aspire 13.0 automatically configures HTTPS endpoints and certificate trust for resources, enabling secure communication during local development without manual certificate setup.
Automatic Certificate Trust (13.0+)
Aspire automatically configures ASP.NET Development Certificate trust for Python, Node.js, and containerized applications:
// No configuration needed - certificate trust is automatic
var pythonApi = builder.AddUvicornApp("api", "./api", "main:app");
var nodeApi = builder.AddJavaScriptApp("frontend", "./frontend");
var container = builder.AddContainer("service", "myimage");Automatic configuration by language:
| Language | Environment Variables Set |
|---|---|
| Python | SSL_CERT_FILE, REQUESTS_CA_BUNDLE |
| Node.js | NODE_EXTRA_CA_CERTS |
| Containers | Certificate bundles mounted, OpenSSL vars configured |
Certificate Trust APIs
Enable/Disable Per Resource
var builder = DistributedApplication.CreateBuilder(args);
// Explicitly enable development certificate trust
var nodeApp = builder.AddNpmApp("frontend", "../frontend")
.WithDeveloperCertificateTrust(trust: true);
// Disable development certificate trust
var pythonApp = builder.AddPythonApp("api", "../api", "main.py")
.WithDeveloperCertificateTrust(trust: false);
builder.Build().Run();Global Configuration
Configure default behavior in appsettings.json:
{
"Aspire": {
"Hosting": {
"DeveloperCertificateTrust": {
"Enabled": true
}
}
}
}Or in AppHost:
builder.Configuration["Aspire:Hosting:DeveloperCertificateTrust:Enabled"] = "false";Custom Certificate Bundles
Create Certificate Collection
var builder = DistributedApplication.CreateBuilder(args);
// Create certificate bundle with custom certificates
var certs = builder.AddCertificateAuthorityCollection("custom-certs")
.WithCertificatesFromFile("./certs/my-ca.pem")
.WithCertificatesFromFile("./certs/another-ca.pem");
// Apply to resources
var api = builder.AddNodeApp("api", "../api", "index.js")
.WithCertificateAuthorityCollection(certs);
builder.Build().Run();Apply to Resources
var gateway = builder.AddYarp("gateway")
.WithCertificateAuthorityCollection(certs)
.WithDeveloperCertificateTrust(trust: true);Certificate Trust Scopes
Control how custom certificates interact with default trusted certificates:
Available Scopes
| Scope | Behavior |
|---|---|
Append | Add custom certs to default trusted certs |
Override | Replace defaults with only custom certs |
System | Combine custom + system root certs (default for Python) |
None | Disable custom certificate trust |
Configure Scope
// Append mode - adds to existing trusted certs
builder.AddNodeApp("api", "../api", "index.js")
.WithCertificateTrustScope(CertificateTrustScope.Append);
// Override mode - replaces all trusted certs
builder.AddContainer("service", "myimage")
.WithCertificateTrustScope(CertificateTrustScope.Override);
// System mode - combines with system root certs (Python default)
builder.AddPythonApp("api", "../api", "main.py")
.WithCertificateTrustScope(CertificateTrustScope.System);
// None mode - disable all custom trust
builder.AddProject<Projects.Api>("api")
.WithCertificateTrustScope(CertificateTrustScope.None);HTTPS Endpoint Configuration
Development Certificate for HTTPS
// Configure HTTPS with development certificate
var api = builder.AddViteApp("frontend", "../frontend")
.WithHttpsDeveloperCertificate();Custom Certificate Callback
builder.AddContainer("api", "myimage")
.WithCertificateTrustConfiguration(context =>
{
// Add custom environment variables
context.EnvironmentVariables["MY_CERT_PATH"] = context.CertificateBundlePath;
// Add command line arguments
context.Arguments.Add("--ca-cert");
context.Arguments.Add(context.CertificateBundlePath);
});Callback Context Properties
| Property | Description |
|---|---|
Scope | The CertificateTrustScope for the resource |
Arguments | Command line arguments list |
EnvironmentVariables | Environment variables dictionary |
CertificateBundlePath | Path to certificate bundle file |
CertificateDirectoriesPath | Paths containing individual certs |
Common Scenarios
Service with HTTPS + Dashboard Telemetry
var api = builder.AddUvicornApp("api", "../api", "main:app")
.WithHttpsEndpoint(port: 8001, env: "HTTPS_PORT")
.WithDeveloperCertificateTrust(trust: true);Container with Custom CA
var certs = builder.AddCertificateAuthorityCollection("corporate-ca")
.WithCertificatesFromFile("./certs/corporate-root.pem");
var service = builder.AddContainer("internal-service", "internal/service")
.WithCertificateAuthorityCollection(certs)
.WithCertificateTrustScope(CertificateTrustScope.System);Disable Certificate Config for Self-Managed Resource
var legacy = builder.AddContainer("legacy", "legacy-service")
.WithDeveloperCertificateTrust(trust: false)
.WithCertificateTrustScope(CertificateTrustScope.None);Container HTTPS (13.1+)
Containers like YARP, Redis, Keycloak, and Uvicorn can serve HTTPS directly:
// YARP with HTTPS
var gateway = builder.AddYarp("gateway")
.WithHttpsDeveloperCertificate();
// Uvicorn with HTTPS
var api = builder.AddUvicornApp("api", "../api", "main:app")
.WithHttpsDeveloperCertificate();Limitations
- Certificate configuration only supported in run mode (not publish mode)
- Not all languages support all trust scope modes
- Python doesn't support
Appendmode natively (usesSystemby default) - .NET on Windows uses
Noneby default (no way to change system store) - HTTPS endpoint APIs are experimental (
ASPIRECERTIFICATES001)
Troubleshooting
Python SSL Errors
Python uses System scope by default. If SSL errors occur:
// Explicitly configure Python certificate trust
builder.AddPythonApp("api", "../api", "main.py")
.WithCertificateTrustScope(CertificateTrustScope.System)
.WithDeveloperCertificateTrust(trust: true);Container SSL Errors
Check that certificate bundle is mounted correctly:
builder.AddContainer("service", "myimage")
.WithCertificateTrustConfiguration(context =>
{
// Log what's being configured
Console.WriteLine($"Bundle: {context.CertificateBundlePath}");
foreach (var env in context.EnvironmentVariables)
{
Console.WriteLine($"{env.Key}={env.Value}");
}
});Node.js Certificate Issues
Ensure NODE_EXTRA_CA_CERTS is being set:
builder.AddJavaScriptApp("frontend", "../frontend")
.WithDeveloperCertificateTrust(trust: true)
.WithEnvironment(ctx =>
{
// Verify NODE_EXTRA_CA_CERTS is set
if (ctx.EnvironmentVariables.TryGetValue("NODE_EXTRA_CA_CERTS", out var path))
{
Console.WriteLine($"Node CA certs: {path}");
}
});Database Integrations Reference
Quick Reference
| Database | Hosting Package | Client Package | Add Method |
|---|---|---|---|
| PostgreSQL | Aspire.Hosting.PostgreSQL | Aspire.Npgsql | AddPostgres() |
| SQL Server | Aspire.Hosting.SqlServer | Aspire.Microsoft.Data.SqlClient | AddSqlServer() |
| MySQL | Aspire.Hosting.MySql | Aspire.MySqlConnector | AddMySql() |
| MongoDB | Aspire.Hosting.MongoDB | Aspire.MongoDB.Driver | AddMongoDB() |
| Oracle | Aspire.Hosting.Oracle | Aspire.Oracle.EntityFrameworkCore | AddOracle() |
| SQLite | N/A | Aspire.Microsoft.Data.Sqlite | N/A |
PostgreSQL
Hosting Integration
// Package: Aspire.Hosting.PostgreSQL
var postgres = builder.AddPostgres("postgres")
.WithDataVolume() // Persist data
.WithPgAdmin(); // Add pgAdmin UI
var db = postgres.AddDatabase("mydb");
builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WaitFor(db);Client Integration
// Package: Aspire.Npgsql
builder.AddNpgsqlDataSource("mydb");
// With Entity Framework
// Package: Aspire.Npgsql.EntityFrameworkCore.PostgreSQL
builder.AddNpgsqlDbContext<MyDbContext>("mydb");Configuration
{
"Aspire": {
"Npgsql": {
"DisableHealthChecks": false,
"DisableTracing": false,
"DisableMetrics": false
}
}
}Use in Services
public class MyService(NpgsqlDataSource dataSource)
{
public async Task<List<Item>> GetItems()
{
await using var conn = await dataSource.OpenConnectionAsync();
// Use connection...
}
}SQL Server
Hosting Integration
// Package: Aspire.Hosting.SqlServer
var sql = builder.AddSqlServer("sql")
.WithDataVolume();
var db = sql.AddDatabase("mydb");
builder.AddProject<Projects.Api>("api")
.WithReference(db)
.WaitFor(db);Client Integration
// Package: Aspire.Microsoft.Data.SqlClient
builder.AddSqlServerClient("mydb");
// With Entity Framework
// Package: Aspire.Microsoft.EntityFrameworkCore.SqlServer
builder.AddSqlServerDbContext<MyDbContext>("mydb");Persistent Password
dotnet user-secrets set Parameters:sql-password "YourPassword123!"var password = builder.AddParameter("sql-password", secret: true);
var sql = builder.AddSqlServer("sql", password: password)
.WithDataVolume();MySQL
Hosting Integration
// Package: Aspire.Hosting.MySql
var mysql = builder.AddMySql("mysql")
.WithDataVolume()
.WithPhpMyAdmin(); // Add phpMyAdmin UI
var db = mysql.AddDatabase("mydb");Client Integration
// Package: Aspire.MySqlConnector
builder.AddMySqlDataSource("mydb");
// With Entity Framework (Pomelo)
// Package: Aspire.Pomelo.EntityFrameworkCore.MySql
builder.AddMySqlDbContext<MyDbContext>("mydb");MongoDB
Hosting Integration
// Package: Aspire.Hosting.MongoDB
var mongo = builder.AddMongoDB("mongo")
.WithDataVolume()
.WithMongoExpress(); // Add Mongo Express UI
var db = mongo.AddDatabase("mydb");Client Integration
// Package: Aspire.MongoDB.Driver
builder.AddMongoDBClient("mydb");Use in Services
public class MyService(IMongoClient client)
{
private readonly IMongoDatabase _db = client.GetDatabase("mydb");
public async Task<List<Item>> GetItems()
{
var collection = _db.GetCollection<Item>("items");
return await collection.Find(_ => true).ToListAsync();
}
}Oracle
Hosting Integration
// Package: Aspire.Hosting.Oracle
var oracle = builder.AddOracle("oracle")
.WithDataVolume();
var db = oracle.AddDatabase("mydb");Client Integration
// Package: Aspire.Oracle.EntityFrameworkCore
builder.AddOracleDatabaseDbContext<MyDbContext>("mydb");Common Patterns
Data Volumes (Persist Data)
// Auto-named volume (recommended)
builder.AddPostgres("db").WithDataVolume();
// Custom volume name
builder.AddPostgres("db")
.WithVolume(name: "pg-data", target: "/var/lib/postgresql/data");Bind Mounts
builder.AddPostgres("db")
.WithDataBindMount(source: "./data/postgres");Health Checks
// Wait for database to be running
api.WaitFor(db);
// Wait for database to be healthy (if health check configured)
api.WaitForHealthy(db);Connection String Override
// Use external database via connection string
var db = builder.AddConnectionString("mydb");
// appsettings.json:
// { "ConnectionStrings": { "mydb": "Host=prod.db.com;..." } }Entity Framework Migrations
Run Migrations on Startup
// In consuming project
public class MigrationService(MyDbContext context) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
await context.Database.MigrateAsync(ct);
}
}Use aspire exec for EF Commands
# Run EF migrations with proper connection string
aspire exec --resource api -- dotnet ef migrations add Init
# Apply migrations
aspire exec --resource api -- dotnet ef database updateDatabase Admin UIs
PostgreSQL - pgAdmin
builder.AddPostgres("postgres").WithPgAdmin();MySQL - phpMyAdmin
builder.AddMySql("mysql").WithPhpMyAdmin();MongoDB - Mongo Express
builder.AddMongoDB("mongo").WithMongoExpress();PostgreSQL - pgWeb
builder.AddPostgres("postgres").WithPgWeb();Environment Variables Injected
When using WithReference(db), Aspire injects:
| Variable | Example |
|---|---|
ConnectionStrings__mydb | Full connection string |
MYDB_HOST | localhost |
MYDB_PORT | 5432 |
MYDB_DATABASE | mydb |
MYDB_USERNAME | postgres |
MYDB_PASSWORD | (secret) |
Polyglot Database Access
Python
import os
import psycopg
conn_str = os.getenv("CONNECTIONSTRINGS__MYDB")
async with await psycopg.AsyncConnection.connect(conn_str) as conn:
# Use connectionJavaScript/Node.js
import pg from 'pg';
const client = new pg.Client({
host: process.env.MYDB_HOST,
port: process.env.MYDB_PORT,
database: process.env.MYDB_DATABASE,
user: process.env.MYDB_USERNAME,
password: process.env.MYDB_PASSWORD,
});
await client.connect();Deployment and CLI Reference
Aspire CLI
Installation
dotnet tool install -g aspireSelf-Update (13.1+)
# Update CLI to latest version
aspire update --self
# Select channel (stable, preview, staging)
aspire update --self --channel previewCommon Commands
| Command | Status | Description |
|---|---|---|
aspire new | Stable | Create new Aspire project from template |
aspire init | Stable | Initialize Aspire in existing project or create single-file AppHost |
aspire add | Stable | Add integration package |
aspire run | Stable | Run the AppHost for local development |
aspire do | Preview | Execute specific pipeline step and dependencies |
aspire deploy | Preview | Deploy to target environment |
aspire publish | Preview | Generate deployment artifacts |
aspire exec | Preview | Execute command with resource context |
aspire mcp | Stable | Manage MCP server for AI assistants |
aspire update | Preview | Update Aspire packages and templates |
aspire new
# Create starter solution (with Web frontend + API)
aspire new aspire-starter -o MyApp
# Create empty solution (AppHost + ServiceDefaults only)
aspire new aspire --name MyApp
# List available templates
aspire new --list
# Create Python starter (FastAPI + React)
aspire new aspire-py-starter -n MyApp -o MyAppAvailable templates:
| Template | Description |
|---|---|
aspire | Empty: AppHost + ServiceDefaults |
aspire-starter | Web frontend + API backend |
aspire-py-starter | Python (FastAPI) + React |
VS Code Extension (GUI alternative): Instead of CLI, use the VS Code extension: 1. Open Command Palette (Cmd/Ctrl + Shift + P) 2. Run Aspire: New Aspire project 3. Select template and specify name/location
aspire init (13.0+)
# Aspirify an existing solution (interactive)
aspire init
# Create single-file AppHost
aspire init --single-file
# Specify source and version
aspire init --source https://api.nuget.org/v3/index.json --version 13.1.0The aspire init command:
- Detects existing projects in your solution
- Creates an AppHost project referencing them
- Sets up service defaults
- Supports single-file AppHost (
.csfile without.csproj)
aspire add
# Interactive search and add
aspire add redis
# Add specific package
aspire add Aspire.Hosting.Redisaspire run
# Run AppHost
aspire run
# Run with specific launch profile
aspire run --launch-profile Production
# Run specific project
aspire run --project ./src/MyApp.AppHostaspire exec
# Run command with resource's connection strings
aspire exec --resource api -- dotnet ef migrations add Init
# Apply migrations
aspire exec --resource api -- dotnet ef database update
# Run with specific project
aspire exec --project ./src/AppHost -- dotnet testaspire do (13.0+ Pipeline System)
The aspire do command executes specific pipeline steps:
# Generate manifest
aspire do publish-manifest --output-path ./aspire-manifest.json
# Build container images
aspire do build
# Push images to registry
aspire do push
# Provision infrastructure only
aspire do provision-infra
# Deploy specific service
aspire do deploy-apiservice
# View available steps
aspire do diagnostics
# Docker Compose operations
aspire do prepare-compose --environment staging
aspire do docker-compose-down-composeWell-Known Steps:
| Step | Description |
|---|---|
deploy | Full deployment (infra + images + deploy) |
publish | Generate deployment artifacts |
build | Build container images |
push | Push images to registry |
publish-manifest | Generate manifest.json |
provision-infra | Provision infrastructure only |
diagnostics | Show available pipeline steps |
aspire deploy
# Deploy to Azure Container Apps
aspire deploy --publisher azure
# Deploy with prompts for configuration
aspire deploy
# Deploy specific project
aspire deploy --project ./src/MyApp.AppHostaspire publish
# Generate manifest.json
aspire publish --output-path ./publish
# Generate with specific publisher
aspire publish --publisher manifest --output-path ./publish
# Generate Kubernetes manifests
aspire publish --publisher kubernetes --output-path ./k8saspire mcp (13.0+)
# Initialize MCP for AI assistants
aspire mcp init
# Start MCP server manually
aspire mcp startSee mcp-integration.md for detailed MCP configuration.
aspire update
# Update packages in current project
aspire update
# Self-update the CLI
aspire update --self
# Select update channel
aspire update --self --channel previewDeployment Targets
Azure Container Apps (Default)
// Configure deployment target
builder.AddProject<Projects.Api>("api")
.PublishAsAzureContainerApp((infra, app) =>
{
app.Template.Value!.Scale.MinReplicas = 1;
app.Template.Value!.Scale.MaxReplicas = 10;
});# Deploy to Azure Container Apps
aspire deploy --publisher azureDocker Compose
// Package: Aspire.Hosting.Docker
builder.AddDockerComposeEnvironment("compose");# Generate and deploy with Docker Compose
aspire do prepare-compose --environment production
aspire deploy
# Clean up
aspire do docker-compose-down-composeDocker / Container Registry
// Publish as Dockerfile
builder.AddProject<Projects.Api>("api")
.PublishAsDockerFile();
// With custom registry
builder.AddAzureContainerRegistry("registry");
builder.AddAzureContainerAppEnvironment("env")
.WithAzureContainerRegistry(registry);Kubernetes / Helm
// Publish as Kubernetes resources
builder.AddProject<Projects.Api>("api")
.PublishAsKubernetes();# Generate Kubernetes manifests
aspire publish --publisher kubernetes --output-path ./k8sManifest Format
The manifest.json file describes resources:
{
"resources": {
"api": {
"type": "project.v0",
"path": "../Api/Api.csproj",
"env": {
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true"
},
"bindings": {
"http": {
"scheme": "http",
"protocol": "tcp",
"transport": "http"
},
"https": {
"scheme": "https",
"protocol": "tcp",
"transport": "http"
}
}
},
"redis": {
"type": "container.v0",
"image": "docker.io/library/redis:7.4",
"bindings": {
"tcp": {
"scheme": "tcp",
"protocol": "tcp",
"transport": "tcp",
"targetPort": 6379
}
}
}
}
}Execution Context
Check Run vs Publish Mode
if (builder.ExecutionContext.IsRunMode)
{
// Local development configuration
redis.WithDataVolume();
}
if (builder.ExecutionContext.IsPublishMode)
{
// Production configuration
api.WithReplicas(3);
}Conditional Resources
// Only in run mode (local dev)
if (builder.ExecutionContext.IsRunMode)
{
builder.AddPostgres("db").WithPgAdmin();
}Azure Deployment Configuration
Required Configuration
{
"Azure": {
"SubscriptionId": "your-subscription-id",
"Location": "eastus",
"ResourceGroup": "rg-myapp",
"AllowResourceGroupCreation": true
}
}Or Environment Variables
export AZURE_SUBSCRIPTION_ID=...
export AZURE_LOCATION=eastus
export AZURE_RESOURCE_GROUP=rg-myappAzure CLI Setup
# Login to Azure
az login
# Set subscription
az account set --subscription "your-subscription-id"
# Verify
az account showCI/CD Integration
GitHub Actions
name: Deploy Aspire App
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install Aspire CLI
run: dotnet tool install -g aspire
- name: Login to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy
run: aspire deploy --publisher azure
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_LOCATION: eastus
AZURE_RESOURCE_GROUP: rg-myappAzure DevOps
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
version: '10.0.x'
- script: dotnet tool install -g aspire
displayName: 'Install Aspire CLI'
- task: AzureCLI@2
inputs:
azureSubscription: 'MyAzureConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
aspire deploy --publisher azure
env:
AZURE_LOCATION: eastus
AZURE_RESOURCE_GROUP: rg-myappCustom Publishers
Create Custom Publisher (Deprecated in 13.0)
// Note: IDistributedApplicationPublisher is deprecated
// Use PipelineStep instead for new code
public class MyCustomPublisher : IDistributedApplicationPublisher
{
public async Task PublishAsync(
DistributedApplicationModel model,
CancellationToken ct)
{
foreach (var resource in model.Resources)
{
// Custom publishing logic
}
}
}Pipeline Steps (13.0+)
// Modern approach using pipeline steps
builder.Services.AddPipelineStep<MyDeployStep>("my-deploy");Bicep Output
Aspire generates Bicep files for Azure resources:
# Generate Bicep
aspire publish --publisher azure --output-path ./infra
# Files generated:
# - main.bicep
# - main.parameters.json
# - <resource>.module.bicep (per resource)Customize Bicep
builder.AddAzureStorage("storage")
.ConfigureInfrastructure(infra =>
{
var storage = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
storage.Sku = new StorageSku(StorageSkuName.StandardGrs);
});Single-File AppHost (13.0+)
Create a minimal AppHost without a project file:
apphost.cs:
#:package Aspire.Hosting@*
#:package Aspire.Hosting.Redis@*
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(cache);
builder.Build().Run();Run with:
aspire run --project ./apphost.csDiagnostics and Troubleshooting Reference
Common Error Codes
ASPIRE001 - Resource Name Invalid
error ASPIRE001: Resource name 'my-resource!' contains invalid charactersFix: Use only alphanumeric characters and hyphens in resource names.
// Bad
builder.AddRedis("my-cache!")
// Good
builder.AddRedis("my-cache")ASPIRE002 - Duplicate Resource Name
error ASPIRE002: Resource 'api' already existsFix: Use unique names for all resources.
ASPIRE003 - Missing Reference
error ASPIRE003: Resource 'db' referenced but not definedFix: Ensure the referenced resource is added before referencing it.
ASPIRE004 - Circular Dependency
error ASPIRE004: Circular dependency detected between resourcesFix: Review WithReference and WaitFor chains to remove cycles.
Container Issues
Container Won't Start
Resource 'redis' failed to start: port already in useFixes: 1. Stop conflicting containers: docker ps and docker stop <id> 2. Use random ports: .WithHttpEndpoint() without port number 3. Change to a different port: .WithHttpEndpoint(port: 6380)
Container Pull Failed
Failed to pull image: docker.io/library/postgres:15Fixes: 1. Check Docker is running: docker info 2. Check network connectivity 3. Try pulling manually: docker pull postgres:15 4. Check Docker Hub rate limits
Container Keeps Restarting
Container 'db' restarted 5 timesFixes: 1. Check container logs in Dashboard 2. Verify environment variables 3. Check resource limits 4. Ensure health checks aren't too aggressive
Service Discovery Issues
Connection String Not Found
ConnectionStrings__db not found in configurationCauses:
- Missing
WithReference(db)call - Resource not started yet
Fix:
builder.AddProject<Projects.Api>("api")
.WithReference(db) // Add this
.WaitFor(db); // And thisService URL Format
Aspire uses specific environment variable patterns:
services__<name>__<scheme>__<index>Example: services__api__http__0 = http://localhost:5001
Debug Service Discovery
// In consuming app, log all env vars
foreach (var env in Environment.GetEnvironmentVariables())
{
Console.WriteLine($"{env.Key}={env.Value}");
}Port Conflicts
Port Already in Use
Address already in use: 0.0.0.0:5432Fixes: 1. Find process: lsof -i :5432 or netstat -ano | findstr :5432 2. Kill process or use different port 3. Use dynamic ports: .WithHttpEndpoint() without port
Proxy Port Issues
IsProxied endpoint not binding correctlyFix: Set IsProxied to false for external executables:
.WithEndpoint(callback: e => e.IsProxied = false)Database Issues
Database Not Created
Database 'mydb' does not existCause: Database creation relies on ResourceReadyEvent
Fixes: 1. Ensure container is fully started 2. Use WaitFor before referencing 3. Check container logs for errors
Migration Errors
Cannot apply migrations: connection refusedFix: Use aspire exec with proper context:
aspire exec --resource api -- dotnet ef database updateAzure Issues
Authentication Failed
Azure authentication failed: no valid credentials foundFixes: 1. Run az login 2. Set subscription: az account set --subscription "..." 3. Check environment variables: AZURE_*
Provisioning Failed
Resource provisioning failed: insufficient permissionsFixes: 1. Verify Contributor role on resource group 2. Check subscription limits/quotas 3. Review Azure Activity Log for details
Missing Configuration
Azure configuration not found: SubscriptionIdFix: Add to appsettings.json:
{
"Azure": {
"SubscriptionId": "your-subscription-id",
"Location": "eastus",
"ResourceGroup": "rg-myapp"
}
}Dashboard Issues
Dashboard Not Loading
Dashboard not accessible at https://localhost:17043Fixes: 1. Check if port is blocked by firewall 2. Try different browser 3. Clear browser cache 4. Check AppHost console for login URL with token
Telemetry Not Showing
No traces/metrics appearing in dashboardFixes: 1. Verify OTEL env vars are set 2. Check service is actually running 3. Wait for data to propagate (few seconds) 4. Verify correct endpoints in service
Health Check Failures
Resource Never Becomes Healthy
Timeout waiting for 'db' to become healthyCauses:
- Resource is unhealthy
- Health check misconfigured
- Network issues
Fixes: 1. Check container logs 2. Verify health check endpoint exists 3. Increase timeout:
.WaitForHealthy(db, timeout: TimeSpan.FromMinutes(5))Debugging Techniques
Enable Verbose Logging
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Aspire": "Debug",
"Aspire.Hosting": "Debug",
"Aspire.Hosting.Dcp": "Debug"
}
}
}View Container Logs
1. Open Aspire Dashboard 2. Navigate to Resources 3. Click on resource 4. View Console Logs tab
Check Resource State
builder.Eventing.Subscribe<ResourceReadyEvent>((@event, ct) =>
{
Console.WriteLine($"Resource {event.Resource.Name} is ready");
return Task.CompletedTask;
});Environment Variable Inspection
// Add to consuming service
app.MapGet("/debug/env", () =>
Environment.GetEnvironmentVariables()
.Cast<DictionaryEntry>()
.Where(e => e.Key.ToString()!.StartsWith("ConnectionStrings") ||
e.Key.ToString()!.StartsWith("services"))
.ToDictionary(e => e.Key.ToString()!, e => e.Value?.ToString()));Common Warnings
ASPIREINTERACTION001
warning ASPIREINTERACTION001: Type is for evaluation purposes onlyContext: Using preview/experimental APIs
Fix: Suppress if intentional:
#pragma warning disable ASPIREINTERACTION001
// Your code
#pragma warning restore ASPIREINTERACTION001Resource Not Starting
Check these in order: 1. Docker running? docker info 2. Image exists? docker images 3. Port available? lsof -i :<port> 4. Container logs in Dashboard 5. AppHost console output
Performance Issues
Slow Container Startup
// Use persistent containers for frequently-used resources
builder.AddRedis("cache")
.WithLifetime(ContainerLifetime.Persistent);Large Context/Memory
- Reduce number of resources
- Use external services instead of containers
- Increase Docker memory limits
Getting Help
1. Check Aspire Dashboard logs 2. Review container logs 3. Enable debug logging 4. Check GitHub Issues: https://github.com/dotnet/aspire/issues 5. Use aspire --help for CLI options
MCP Integration Reference
Overview
Aspire provides Model Context Protocol (MCP) integration, enabling AI assistants (Claude Code, GitHub Copilot, Cursor, VS Code) to interact with your running Aspire applications. This enables agentic development where AI can query resources, access telemetry, and execute commands.
AI Assistants: Use These Tools
When debugging Aspire apps, ALWAYS use MCP tools instead of curl or external commands.
User: "Is my api service running?"
→ Use: list_resources (shows all resource states)
→ DON'T: curl, docker ps, or suggest checking dashboard manually
User: "Show me the logs for my api"
→ Use: list_console_logs with resource name "api"
→ DON'T: docker logs, curl to dashboard API
User: "What errors happened recently?"
→ Use: list_traces to find failed traces, then list_trace_structured_logs
→ DON'T: grep log files, curl OTLP endpoints
User: "Which integrations can I use for Redis?"
→ Use: list_integrations, then get_integration_docs
→ DON'T: web search (MCP has current docs)Quick Setup
Using aspire mcp init (Recommended)
# Navigate to your AppHost directory
cd ./MyApp.AppHost
# Initialize MCP configuration
aspire mcp initThe command detects supported agent environments and creates configuration files:
Which agent environments do you want to configure?
[ ] Configure VS Code to use the Aspire MCP server
[ ] Configure GitHub Copilot CLI to use Aspire MCP server
[ ] Configure Claude Code to use Aspire MCP server
[ ] Configure Open Code to use Aspire MCP server
Which additional options do you want to enable?
[ ] Create an agent instructions file (AGENTS.md)
[ ] Configure Playwright MCP serverManual Configuration
1. Run your Aspire app 2. Open the Dashboard and click the MCP button (top right) 3. Use the dialog details to configure your AI assistant
Required settings:
url: Aspire MCP endpoint addresstype:http(streamable-HTTP MCP server)x-mcp-api-key: HTTP header for authentication
AI Assistant Configuration
Claude Code
Add to your Claude Code MCP configuration:
{
"mcpServers": {
"aspire": {
"type": "http",
"url": "https://localhost:16036/mcp",
"headers": {
"x-mcp-api-key": "${input:aspire-api-key}"
}
}
}
}VS Code (GitHub Copilot)
Add to your VS Code settings or mcp.json:
{
"mcp": {
"servers": {
"aspire": {
"type": "http",
"url": "https://localhost:16036/mcp",
"headers": {
"x-mcp-api-key": "${input:aspire-api-key}"
}
}
}
}
}Cursor
Add to your Cursor MCP configuration following Cursor's MCP server documentation.
MCP Tools
The Aspire MCP server provides these tools:
Resource Management
| Tool | Description |
|---|---|
list_resources | Lists all resources with state, health, endpoints, and commands |
execute_resource_command | Executes a command on a resource (start, stop, restart) |
Telemetry
| Tool | Description |
|---|---|
list_console_logs | Lists console logs for a resource |
list_structured_logs | Lists structured logs, optionally filtered by resource |
list_traces | Lists distributed traces, optionally filtered by resource |
list_trace_structured_logs | Lists structured logs for a specific trace |
AppHost Management
| Tool | Description |
|---|---|
list_apphosts | Lists all AppHosts in the workspace |
select_apphost | Selects which AppHost to use when multiple exist |
Integration Discovery (13.1+)
| Tool | Description |
|---|---|
list_integrations | Lists available Aspire hosting integrations |
get_integration_docs | Gets documentation for a specific integration package |
Excluding Resources from MCP
Exclude sensitive resources from MCP access:
var builder = DistributedApplication.CreateBuilder(args);
// This resource won't be visible to AI assistants
var secrets = builder.AddProject<Projects.SecretsService>("secrets")
.ExcludeFromMcp();
// This resource is visible (default)
var api = builder.AddProject<Projects.Api>("api");
builder.Build().Run();Dashboard MCP Configuration
Environment Variables
| Variable | Description |
|---|---|
ASPIRE_DASHBOARD_MCP_ENDPOINT_URL | MCP endpoint URL (e.g., https://localhost:16036) |
Dashboard:Mcp:AuthMode | ApiKey or Unsecured (default: Unsecured) |
Dashboard:Mcp:PrimaryApiKey | Primary API key for authentication |
Dashboard:Mcp:SecondaryApiKey | Optional secondary API key |
Dashboard:Mcp:Disabled | Set to true to disable MCP server |
Dashboard:Mcp:PublicUrl | Public URL when behind a proxy |
Configuration in appsettings.json
{
"Dashboard": {
"Mcp": {
"AuthMode": "ApiKey",
"PrimaryApiKey": "your-secure-api-key"
}
}
}Troubleshooting
HTTPS Certificate Issues
Some AI assistants don't support self-signed certificates. Configure HTTP-only MCP:
launchSettings.json:
{
"profiles": {
"https": {
"environmentVariables": {
"ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:16036",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
}
}
}
}Connection Issues
1. Ensure Aspire app is running 2. Verify MCP endpoint URL matches dashboard configuration 3. Check API key is correctly configured 4. Review AI assistant logs for connection errors
Data Truncation
AI models have token limits. Aspire MCP may truncate:
- Large exception stack traces
- Large telemetry collections (older items omitted)
Example Prompts
Once configured, try these prompts with your AI assistant:
- "Are all my resources running?"
- "Show me the logs for the api resource"
- "What errors have occurred in the last 5 minutes?"
- "List the traces for slow requests"
- "What integrations are available for Redis?"
CLI Commands
Start MCP Server Manually
aspire mcp startInitialize MCP Configuration
# Interactive setup
aspire mcp init
# With specific project
aspire mcp init --project ./MyApp.AppHostMessaging Integrations Reference
Quick Reference
| Messaging | Hosting Package | Client Package | Add Method |
|---|---|---|---|
| RabbitMQ | Aspire.Hosting.RabbitMQ | Aspire.RabbitMQ.Client | AddRabbitMQ() |
| Kafka | Aspire.Hosting.Kafka | Aspire.Confluent.Kafka | AddKafka() |
| NATS | Aspire.Hosting.NATS | Aspire.NATS.Net | AddNats() |
| Azure Service Bus | Aspire.Hosting.Azure.ServiceBus | Aspire.Azure.Messaging.ServiceBus | AddAzureServiceBus() |
| Azure Event Hubs | Aspire.Hosting.Azure.EventHubs | Aspire.Azure.Messaging.EventHubs | AddAzureEventHubs() |
RabbitMQ
Hosting Integration
// Package: Aspire.Hosting.RabbitMQ
var rabbitmq = builder.AddRabbitMQ("messaging")
.WithDataVolume() // Persist data
.WithLifetime(ContainerLifetime.Persistent) // Keep across restarts
.WithManagementPlugin(); // Enable management UI
builder.AddProject<Projects.Api>("api")
.WithReference(rabbitmq)
.WaitFor(rabbitmq);Client Integration
// Package: Aspire.RabbitMQ.Client
builder.AddRabbitMQClient("messaging");Use in Services
public class MessagePublisher(IConnection connection)
{
public void Publish(string queue, string message)
{
using var channel = connection.CreateModel();
channel.QueueDeclare(queue, durable: true, exclusive: false, autoDelete: false);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "", routingKey: queue, body: body);
}
}With MassTransit
// Package: MassTransit.RabbitMQ
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
var connectionString = context.GetRequiredService<IConfiguration>()
.GetConnectionString("messaging");
cfg.Host(new Uri(connectionString!));
cfg.ConfigureEndpoints(context);
});
});Apache Kafka
Hosting Integration
// Package: Aspire.Hosting.Kafka
var kafka = builder.AddKafka("kafka")
.WithDataVolume()
.WithKafkaUI(); // Add Kafka UI
builder.AddProject<Projects.Api>("api")
.WithReference(kafka)
.WaitFor(kafka);Client Integration
// Package: Aspire.Confluent.Kafka
// Producer
builder.AddKafkaProducer<string, string>("kafka");
// Consumer
builder.AddKafkaConsumer<string, string>("kafka", consumerBuilder =>
{
consumerBuilder.Config.GroupId = "my-consumer-group";
consumerBuilder.Config.AutoOffsetReset = AutoOffsetReset.Earliest;
});Use in Services
// Producer
public class EventPublisher(IProducer<string, string> producer)
{
public async Task PublishAsync(string topic, string key, string value)
{
await producer.ProduceAsync(topic, new Message<string, string>
{
Key = key,
Value = value
});
}
}
// Consumer
public class EventConsumer(IConsumer<string, string> consumer) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
consumer.Subscribe("my-topic");
while (!ct.IsCancellationRequested)
{
var result = consumer.Consume(ct);
// Process message
}
}
}NATS
Hosting Integration
// Package: Aspire.Hosting.NATS
var nats = builder.AddNats("nats")
.WithDataVolume()
.WithJetStream(); // Enable JetStream for persistence
builder.AddProject<Projects.Api>("api")
.WithReference(nats)
.WaitFor(nats);Client Integration
// Package: Aspire.NATS.Net
builder.AddNatsClient("nats");Use in Services
public class NatsPublisher(INatsConnection nats)
{
public async Task PublishAsync(string subject, string data)
{
await nats.PublishAsync(subject, data);
}
}
public class NatsSubscriber(INatsConnection nats) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var msg in nats.SubscribeAsync<string>("events.*", cancellationToken: ct))
{
Console.WriteLine($"Received: {msg.Data}");
}
}
}Azure Service Bus
Hosting Integration
// Package: Aspire.Hosting.Azure.ServiceBus
var serviceBus = builder.AddAzureServiceBus("messaging");
// Add queue
serviceBus.AddServiceBusQueue("orders");
// Add topic with subscription
var topic = serviceBus.AddServiceBusTopic("notifications");
topic.AddServiceBusSubscription("email-handler");
// Use emulator locally
builder.AddAzureServiceBus("messaging").RunAsEmulator();Client Integration
// Package: Aspire.Azure.Messaging.ServiceBus
builder.AddAzureServiceBusClient("messaging");Use in Services
public class OrderPublisher(ServiceBusClient client)
{
public async Task SendOrderAsync(Order order)
{
var sender = client.CreateSender("orders");
await sender.SendMessageAsync(new ServiceBusMessage(
JsonSerializer.Serialize(order)));
}
}
public class OrderProcessor(ServiceBusClient client) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
var processor = client.CreateProcessor("orders");
processor.ProcessMessageAsync += async args =>
{
var order = JsonSerializer.Deserialize<Order>(args.Message.Body);
// Process order
await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args => Task.CompletedTask;
await processor.StartProcessingAsync(ct);
}
}Azure Event Hubs
Hosting Integration
// Package: Aspire.Hosting.Azure.EventHubs
var eventHubs = builder.AddAzureEventHubs("events");
eventHubs.AddEventHub("telemetry");
// Use emulator locally
builder.AddAzureEventHubs("events").RunAsEmulator();Client Integration
// Package: Aspire.Azure.Messaging.EventHubs
// Producer
builder.AddAzureEventHubProducerClient("events", "telemetry");
// Consumer
builder.AddAzureEventHubConsumerClient("events", "telemetry");LavinMQ (Lightweight RabbitMQ Alternative)
Hosting Integration
// Package: CommunityToolkit.Aspire.Hosting.LavinMQ
var lavinmq = builder.AddLavinMQ("messaging")
.WithDataVolume()
.WithManagementPlugin();Common Patterns
Persistent Messaging Container
builder.AddRabbitMQ("messaging")
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);Management UIs
// RabbitMQ Management
builder.AddRabbitMQ("messaging").WithManagementPlugin();
// Kafka UI
builder.AddKafka("kafka").WithKafkaUI();Health Checks
// Wait for messaging to be ready
builder.AddProject<Projects.Api>("api")
.WithReference(messaging)
.WaitFor(messaging);Environment Variables
When using WithReference(messaging):
| Service | Variable | Example |
|---|---|---|
| RabbitMQ | ConnectionStrings__messaging | amqp://guest:guest@localhost:5672 |
| Kafka | ConnectionStrings__kafka | localhost:9092 |
| NATS | ConnectionStrings__nats | nats://localhost:4222 |
Polyglot Integrations Reference
Quick Reference
| Language/Runtime | Package | Add Method |
|---|---|---|
| JavaScript (Generic) | Aspire.Hosting.JavaScript | AddJavaScriptApp() |
| Node.js | Aspire.Hosting.JavaScript | AddNodeApp() |
| Vite | Aspire.Hosting.JavaScript | AddViteApp() |
| Angular | Aspire.Hosting.JavaScript | AddAngularApp() |
| Python | Aspire.Hosting.Python | AddPythonProject() |
| Uvicorn | Aspire.Hosting.Python | AddUvicornApp() |
| Go | CommunityToolkit.Aspire.Hosting.Golang | AddGolangApp() |
| Rust | CommunityToolkit.Aspire.Hosting.Rust | AddRustApp() |
| Java/Spring | CommunityToolkit.Aspire.Hosting.Java | AddSpringApp() |
| Deno | CommunityToolkit.Aspire.Hosting.Deno | AddDenoApp() |
JavaScript/Node.js (13.0+)
Package
# Aspire 13.0 renamed NodeJs to JavaScript
dotnet add package Aspire.Hosting.JavaScriptAddJavaScriptApp (Recommended)
The foundational method for all JavaScript applications:
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject<Projects.Api>("api");
// Basic usage - runs "npm run dev" by default
var frontend = builder.AddJavaScriptApp("frontend", "./frontend")
.WithHttpEndpoint(port: 3000, env: "PORT")
.WithReference(api);
builder.Build().Run();Default behavior:
- Uses npm as package manager (detects from
package.json) - Runs "dev" script during local development
- Runs "build" script when publishing
- Automatically generates Dockerfiles for production
Package Manager Configuration
// npm (default)
var app = builder.AddJavaScriptApp("app", "./app");
// npm with custom flags
var npmApp = builder.AddJavaScriptApp("app", "./app")
.WithNpm(installCommand: "ci", installArgs: ["--legacy-peer-deps"]);
// Yarn
var yarnApp = builder.AddJavaScriptApp("app", "./app")
.WithYarn();
// Yarn with flags
var yarnCustom = builder.AddJavaScriptApp("app", "./app")
.WithYarn(installArgs: ["--immutable"]);
// pnpm
var pnpmApp = builder.AddJavaScriptApp("app", "./app")
.WithPnpm();
// pnpm with flags
var pnpmCustom = builder.AddJavaScriptApp("app", "./app")
.WithPnpm(installArgs: ["--frozen-lockfile"]);Production lockfile behavior:
- npm: Uses
npm ciifpackage-lock.jsonexists - yarn v2+: Uses
yarn install --immutableifyarn.lockexists - yarn v1: Uses
yarn install --frozen-lockfile - pnpm: Uses
pnpm install --frozen-lockfileifpnpm-lock.yamlexists
Script Configuration
// Customize scripts
var app = builder.AddJavaScriptApp("app", "./app")
.WithRunScript("start") // Run "npm run start" during dev (default: "dev")
.WithBuildScript("prod"); // Run "npm run prod" when publishing (default: "build")Passing Arguments to Scripts
Option 1: WithArgs
builder.AddJavaScriptApp("frontend", "./frontend")
.WithRunScript("dev")
.WithArgs("--port", "3000", "--host");Option 2: package.json custom scripts
{
"scripts": {
"dev": "vite",
"dev:custom": "vite --port 3000 --host"
}
}builder.AddJavaScriptApp("frontend", "./frontend")
.WithRunScript("dev:custom");AddViteApp (Vite Projects)
Optimized for Vite-based applications (React, Vue, Svelte):
var frontend = builder.AddViteApp("frontend", "./frontend")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.WithReference(api);Vite-specific features:
- Automatic browser opening support
- Hot module replacement (HMR) configuration
- Vite environment variable injection
- Development/production mode detection
AddNodeApp (Node.js Scripts)
Run a Node.js script directly:
builder.AddNodeApp("api", "../node-app", "index.js")
.WithHttpEndpoint(port: 3000, env: "PORT")
.WithExternalHttpEndpoints();AddAngularApp
builder.AddAngularApp("frontend", "../angular-app")
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints();Dynamic Dockerfile Generation
JavaScript apps automatically generate production Dockerfiles:
var app = builder.AddJavaScriptApp("app", "./app");
// Dockerfile generated automatically when publishingGenerated Dockerfile features:
- Detects Node.js version from
.nvmrc,.node-version,package.json, or.tool-versions - Uses multi-stage builds for smaller images
- Installs dependencies in separate layer for caching
- Runs build script to create production assets
- Defaults to
node:22-slimif no version specified
Environment Variables in JavaScript
// Access Aspire-injected variables
const apiUrl = process.env.services__api__http__0;
const dbConn = process.env.ConnectionStrings__db;
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});Python
Package
dotnet add package Aspire.Hosting.PythonWhich Method to Use?
| Framework | Method | Why |
|---|---|---|
| FastAPI | AddUvicornApp | FastAPI is ASGI, needs Uvicorn |
| Starlette | AddUvicornApp | ASGI framework |
| Flask | AddUvicornApp | Modern deployment uses Uvicorn |
| Scripts (no web) | AddPythonProject | Standalone Python scripts |
| CLI tools | AddPythonProject | Non-web Python apps |
IMPORTANT: The method isAddUvicornApp, NOTAddPythonUvicornApp. There is noAddPythonUvicornAppmethod.
AddUvicornApp (FastAPI, Starlette, Flask)
// For ASGI/WSGI web apps - THIS IS THE CORRECT METHOD
builder.AddUvicornApp("api", "../fastapi-app", "main:app")
.WithHttpEndpoint(port: 8000, env: "PORT")
.WithExternalHttpEndpoints();AddPythonProject (Scripts, CLI tools)
// ONLY for non-web Python scripts
builder.AddPythonProject("worker", "../python-worker", "main.py")
.WithEnvironment("SOME_VAR", "value");Package Management
// Use existing venv
builder.AddPythonProject("api", "../python-api", "main.py")
.WithVirtualEnvironment("../python-api/.venv");
// Or create venv and install requirements
builder.AddPythonProject("api", "../python-api", "main.py")
.WithPipPackageInstallation();Supported package managers:
pip(default)uv(faster alternative)venv
Automatic Dockerfile Generation
Python apps also generate production-ready Dockerfiles:
var api = builder.AddUvicornApp("api", "../api", "main:app");
// Dockerfile generated with uvicorn configurationEnvironment Variables in Python
import os
# Access Aspire-injected variables
api_url = os.getenv("services__api__http__0")
db_conn = os.getenv("ConnectionStrings__db")
port = int(os.getenv("PORT", "8000"))Python Debugging (VS Code)
Python resources support debugging automatically:
- Set breakpoints in VS Code
- Run with Aspire debugger
- Breakpoints hit in Python code
Go
Package (Community Toolkit)
dotnet add package CommunityToolkit.Aspire.Hosting.GolangGo App
builder.AddGolangApp("api", "../go-app")
.WithHttpEndpoint(port: 8080, env: "PORT")
.WithExternalHttpEndpoints();Go Code
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go!")
})
http.ListenAndServe(":"+port, nil)
}Rust
Package (Community Toolkit)
dotnet add package CommunityToolkit.Aspire.Hosting.RustRust App
builder.AddRustApp("api", "../rust-app", "my-crate")
.WithHttpEndpoint(port: 8080, env: "PORT")
.WithExternalHttpEndpoints();Java/Spring Boot
Package (Community Toolkit)
dotnet add package CommunityToolkit.Aspire.Hosting.JavaSpring Boot App
// Requires OpenTelemetry Java agent
builder.AddSpringApp("api", "../spring-app",
"../agents/opentelemetry-javaagent.jar")
.WithHttpEndpoint(port: 8080)
.WithExternalHttpEndpoints();Deno
Package (Community Toolkit)
dotnet add package CommunityToolkit.Aspire.Hosting.DenoDeno App
builder.AddDenoApp("api", "../deno-app", "main.ts",
["--allow-net", "--allow-env"])
.WithHttpEndpoint(port: 8000, env: "PORT")
.WithExternalHttpEndpoints();Containers (Any Language)
Generic Container
builder.AddContainer("api", "my-image:latest")
.WithHttpEndpoint(port: 8080, targetPort: 80)
.WithEnvironment("CONFIG_KEY", "value");Dockerfile Build
builder.AddDockerfile("api", "../app")
.WithHttpEndpoint(port: 8080, targetPort: 80)
.WithBuildArg("BUILD_ENV", "production");Common Patterns
Reference Backend from Frontend
var api = builder.AddProject<Projects.Api>("api");
builder.AddViteApp("frontend", "../frontend")
.WithReference(api) // Injects API URL as env var
.WithExternalHttpEndpoints();Pass Database to Polyglot App
var db = builder.AddPostgres("db").AddDatabase("mydb");
builder.AddPythonProject("api", "../python-api", "main.py")
.WithReference(db) // Injects ConnectionStrings__mydb
.WaitFor(db);External HTTP Endpoints
// Required for dashboard to show clickable URLs
builder.AddNodeApp("api", "../app", "index.js")
.WithExternalHttpEndpoints();Port Configuration
// Random port (recommended for services)
.WithHttpEndpoint(env: "PORT")
// Fixed port (for frontends)
.WithHttpEndpoint(port: 3000, env: "PORT")
// Container port mapping
.WithHttpEndpoint(port: 8080, targetPort: 80)Service Discovery
Aspire injects service URLs as environment variables:
| Pattern | Variable |
|---|---|
services__<name>__<scheme>__<index> | Full URL |
ConnectionStrings__<name> | Connection string |
Example for frontend referencing API:
// In Node.js/JavaScript
const apiUrl = process.env.services__api__http__0;
// Result: "http://localhost:5001"# In Python
api_url = os.getenv("services__api__http__0")
# Result: "http://localhost:5001"Certificate Trust (13.0+)
Polyglot apps automatically trust development certificates:
// Automatic - no configuration needed
var pythonApi = builder.AddUvicornApp("api", "./api", "main:app");
var nodeApi = builder.AddJavaScriptApp("frontend", "./frontend");See certificate-config.md for advanced certificate configuration.
Migration from AddNpmApp (9.x)
// Before (9.x) - REMOVED
builder.AddNpmApp("frontend", "../app", "dev", args: ["--no-open"]);
// After (13.0)
builder.AddJavaScriptApp("frontend", "../app")
.WithRunScript("dev")
.WithArgs("--no-open");Testing with Aspire Reference
Package Installation
dotnet add package Aspire.Hosting.TestingBasic Integration Test
using Aspire.Hosting.Testing;
public class IntegrationTests
{
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyApp_AppHost>();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync();
var resourceNotificationService = app.Services
.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
await resourceNotificationService
.WaitForResourceAsync("webfrontend", KnownResourceStates.Running)
.WaitAsync(TimeSpan.FromSeconds(30));
var response = await httpClient.GetAsync("/");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}Test Project Setup
Project File
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApp.AppHost\MyApp.AppHost.csproj" />
</ItemGroup>
</Project>DistributedApplicationTestingBuilder
Create Test Builder
// Reference AppHost project
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyApp_AppHost>();
// With arguments (flows to .NET configuration system)
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyApp_AppHost>(
[
"--environment=Testing",
"UseVolumes=false"
]);Passing Arguments to Disable Volumes
// In test: disable volumes via arguments
using var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AppHost>(["UseVolumes=false"]);
// In AppHost: read from configuration
var builder = DistributedApplication.CreateBuilder(args);
var useVolumes = builder.Configuration["UseVolumes"] != "false";
var postgres = builder.AddPostgres("db");
if (useVolumes)
{
postgres.WithDataVolume();
}Configure Services
appHost.Services.AddHttpClient();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});Build and Start
await using var app = await appHost.BuildAsync();
await app.StartAsync();Creating HTTP Clients
Get Client for Resource
// Create client for specific resource
var httpClient = app.CreateHttpClient("api");
// Make requests
var response = await httpClient.GetAsync("/health");Get Connection String
// Get connection string for resource
var connectionString = await app.GetConnectionStringAsync("db");Waiting for Resources
Wait for Running State
var resourceNotificationService = app.Services
.GetRequiredService<ResourceNotificationService>();
await resourceNotificationService
.WaitForResourceAsync("api", KnownResourceStates.Running)
.WaitAsync(TimeSpan.FromSeconds(60));Wait for Healthy State
await resourceNotificationService
.WaitForResourceHealthyAsync("api")
.WaitAsync(TimeSpan.FromSeconds(60));Known Resource States
KnownResourceStates.Running
KnownResourceStates.Finished
KnownResourceStates.FailedToStart
KnownResourceStates.Exited
KnownResourceStates.Starting
KnownResourceStates.Stopping
KnownResourceStates.Waiting
KnownResourceStates.HiddenTest Fixtures
xUnit Class Fixture
public class AppHostFixture : IAsyncLifetime
{
public DistributedApplication? App { get; private set; }
public async Task InitializeAsync()
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyApp_AppHost>();
App = await appHost.BuildAsync();
await App.StartAsync();
var resourceNotificationService = App.Services
.GetRequiredService<ResourceNotificationService>();
await resourceNotificationService
.WaitForResourceAsync("api", KnownResourceStates.Running)
.WaitAsync(TimeSpan.FromSeconds(60));
}
public async Task DisposeAsync()
{
if (App != null)
{
await App.StopAsync();
await App.DisposeAsync();
}
}
}
public class IntegrationTests : IClassFixture<AppHostFixture>
{
private readonly AppHostFixture _fixture;
public IntegrationTests(AppHostFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task ApiReturnsOk()
{
var client = _fixture.App!.CreateHttpClient("api");
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}Testing with Containers
Wait for Container Ready
// Containers might take longer to start
await resourceNotificationService
.WaitForResourceAsync("redis", KnownResourceStates.Running)
.WaitAsync(TimeSpan.FromMinutes(2));Database Tests
[Fact]
public async Task DatabaseConnectionWorks()
{
var connectionString = await app.GetConnectionStringAsync("db");
await using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync();
var result = await connection.ExecuteScalarAsync<int>(
"SELECT COUNT(*) FROM users");
Assert.True(result >= 0);
}Configuration Override
Override for Tests
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyApp_AppHost>();
// Add test configuration
appHost.Configuration["TestMode"] = "true";
appHost.Configuration["ConnectionStrings:Override"] = "test-connection";Environment Variables
appHost.Services.Configure<EnvironmentOptions>(options =>
{
options.Environment = "Testing";
});Resource Inspection
Get Resource Information
var resources = app.Services.GetRequiredService<DistributedApplicationModel>();
foreach (var resource in resources.Resources)
{
Console.WriteLine($"Resource: {resource.Name}, Type: {resource.GetType().Name}");
}Get Endpoint URLs
// Get allocated endpoints
var endpoints = resource.GetEndpoints();
foreach (var endpoint in endpoints)
{
Console.WriteLine($"Endpoint: {endpoint.EndpointName} - {endpoint.Url}");
}Testing Patterns
Health Check Test
[Fact]
public async Task AllServicesHealthy()
{
var client = app.CreateHttpClient("api");
var response = await client.GetAsync("/health");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Healthy", content);
}API Integration Test
[Fact]
public async Task CreateItemReturnsCreated()
{
var client = app.CreateHttpClient("api");
var item = new { Name = "Test Item", Value = 42 };
var response = await client.PostAsJsonAsync("/items", item);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var created = await response.Content.ReadFromJsonAsync<Item>();
Assert.Equal("Test Item", created!.Name);
}Database Seeding Test
[Fact]
public async Task SeededDataExists()
{
var connectionString = await app.GetConnectionStringAsync("db");
await using var context = new AppDbContext(
new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(connectionString)
.Options);
var count = await context.Users.CountAsync();
Assert.True(count > 0, "Database should have seeded data");
}Troubleshooting Tests
Enable Logging
appHost.Services.AddLogging(builder =>
{
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddConsole();
});Capture Container Logs
var logs = await app.GetResourceLogsAsync("redis");
Console.WriteLine(logs);Increase Timeouts
// For slow containers or CI environments
await resourceNotificationService
.WaitForResourceAsync("db", KnownResourceStates.Running)
.WaitAsync(TimeSpan.FromMinutes(5));DistributedApplicationFactory (Advanced)
For scenarios requiring more control over the AppHost lifecycle than DistributedApplicationTestingBuilder provides:
When to Use
- Need to run code BEFORE the builder is created
- Need to intercept AFTER build but BEFORE start
- Need custom lifecycle hooks during test setup
Custom Factory Class
public class CustomAppHostFactory : DistributedApplicationFactory
{
public CustomAppHostFactory()
: base(typeof(Projects.MyApp_AppHost))
{
}
protected override void OnBuilderCreating(DistributedApplicationOptions options)
{
// Before builder is created
// Modify options here
}
protected override void OnBuilderCreated(IDistributedApplicationBuilder builder)
{
// After builder created, before resources added
// Add test-specific services
builder.Services.AddSingleton<ITestService, MockTestService>();
}
protected override void OnBuilding(DistributedApplicationBuilder builder)
{
// After resources added, before Build()
}
protected override void OnBuilt(DistributedApplication application)
{
// After Build() but before Start()
// Good for final configuration
}
}Usage in Tests
public class AdvancedIntegrationTests : IAsyncLifetime
{
private readonly CustomAppHostFactory _factory = new();
private DistributedApplication? _app;
public async Task InitializeAsync()
{
_app = await _factory.CreateAsync();
await _app.StartAsync();
}
public async Task DisposeAsync()
{
if (_app != null)
{
await _app.StopAsync();
await _app.DisposeAsync();
}
_factory.Dispose();
}
[Fact]
public async Task TestWithCustomFactory()
{
var client = _app!.CreateHttpClient("api");
// ...
}
}Note: DistributedApplicationTestingBuilder uses DistributedApplicationFactory internally. Use the factory directly only when you need the additional lifecycle hooks.
VS Code Extension Reference
Overview
The Aspire VS Code extension brings Aspire CLI features directly into VS Code, providing commands for project creation, integration management, multi-language debugging, and deployment.
Prerequisites
- Aspire CLI installed and available on PATH
- Verify with:
aspire --version
Installation
1. Open VS Code 2. Open Extensions view: View > Extensions (or Cmd+Shift+X / Ctrl+Shift+X) 3. Search for "aspire" 4. Select Aspire extension by Microsoft 5. Click Install
Or install from VS Code Marketplace.
Available Commands
Access commands via Command Palette (Cmd+Shift+P / Ctrl+Shift+P), then type "Aspire":
| Command | Description | Status |
|---|---|---|
| Aspire: New Aspire project | Create new AppHost or starter app from template | Available |
| Aspire: Add an integration | Add hosting integration (Aspire.Hosting.*) | Available |
| Aspire: Configure launch.json | Add Aspire debugger launch configuration | Available |
| Aspire: Manage configuration settings | Manage settings including feature flags | Available |
| Aspire: Open Aspire terminal | Open terminal for Aspire CLI commands | Available |
| Aspire: Publish deployment artifacts | Generate deployment artifacts | Preview |
| Aspire: Deploy app | Deploy to defined targets | Preview |
Key Features
Project Creation
Use Aspire: New Aspire project to create:
- Starter projects: Full solutions with frontend, API, and AppHost
- Empty projects: Minimal AppHost for existing solutions
- Language-specific starters: Python + React, FastAPI, etc.
Integration Management
Use Aspire: Add an integration to add integrations interactively:
1. Search for integration (e.g., "redis", "postgres") 2. Select from available packages 3. Extension updates your AppHost project
Multi-Language Debugging
Debug C#, Python, and JavaScript resources simultaneously:
1. Run Aspire: Configure launch.json to set up debugging 2. Press F5 to start debugging 3. Set breakpoints in any supported language 4. Debug across service boundaries
Supported debugging scenarios:
- .NET projects (C#)
- Python scripts and modules
- Python Flask applications
- Uvicorn/FastAPI applications
- Node.js/JavaScript applications
Deployment
Preview features for deployment:
- Publish deployment artifacts: Generate Docker Compose, Kubernetes manifests, or Azure Bicep
- Deploy app: Deploy directly to configured targets
launch.json Configuration
The extension generates launch configurations automatically. Example:
{
"version": "0.2.0",
"configurations": [
{
"name": "Aspire: MyApp.AppHost",
"type": "aspire",
"request": "launch",
"project": "${workspaceFolder}/MyApp.AppHost/MyApp.AppHost.csproj"
}
]
}Manual Configuration
Add to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Aspire App",
"type": "aspire",
"request": "launch",
"project": "${workspaceFolder}/src/MyApp.AppHost/MyApp.AppHost.csproj",
"launchProfile": "https"
}
]
}Aspire Terminal
The Aspire: Open Aspire terminal command opens a preconfigured terminal for Aspire CLI commands:
- Environment variables set correctly
- PATH includes Aspire CLI
- Ready for commands like
aspire run,aspire deploy
Debugging Workflows
Debug All Resources
1. Set breakpoints in C#, Python, or JavaScript files 2. Press F5 or click Run and Debug 3. Extension launches AppHost and attaches to all debuggable resources 4. Hit breakpoints in any language
Debug Specific Resource
1. In the Debug view, click the resource dropdown 2. Select specific resource to debug 3. Only that resource will have debugger attached
Python Debugging Tips
Python resources are automatically configured for debugging:
// Python debugging is enabled by default
var api = builder.AddUvicornApp("api", "../api", "main:app");
// VS Code can attach debugger automaticallyJavaScript Debugging Tips
JavaScript debugging requires Node.js inspect mode:
var frontend = builder.AddJavaScriptApp("frontend", "../frontend")
.WithArgs("--inspect"); // Enable Node.js debuggingWorking with MCP
The extension integrates with MCP for AI-assisted development:
1. Run aspire mcp init in the Aspire terminal 2. Select VS Code when prompted 3. GitHub Copilot can now query your Aspire application
Tips and Best Practices
Workspace Setup
- Open folder containing your
.slnor AppHost project - Extension auto-detects Aspire projects
Multiple AppHosts
If workspace contains multiple AppHosts:
- Extension prompts which to use
- Or specify in launch.json:
"project": "path/to/specific/AppHost.csproj"
Troubleshooting
Extension commands not appearing:
- Verify CLI installed:
aspire --version - Restart VS Code after CLI installation
Debugging not attaching:
- Ensure launch.json is configured
- Check Debug Console for errors
- Verify resource supports debugging (not all containers do)
Integration not found:
- Run
aspire addin terminal for interactive search - Check NuGet for package name
Feedback
Report issues or request features: 1. Visit Aspire GitHub repository 2. Create new issue with area-extension label