Subchapter 33.1
references/architecture.mdMarkdown10 KBView on GitHub
This reference covers Aspire’s internal architecture: the DCP engine, resource model, service discovery, networking, telemetry, and the eventing system.
The DCP is the runtime engine that Aspire uses in aspire run mode. Key facts:
| Aspect | DCP (local dev) | Kubernetes (production) |
|---|---|---|
| API | Kubernetes-compatible | Full Kubernetes API |
| Scope | Single machine | Cluster |
| Networking | Local proxy, auto ports | Service mesh, ingress |
| Storage | Local volumes | PVCs, cloud storage |
| Purpose | Developer inner loop | Production deployment |
The Kubernetes-compatible API means Aspire understands the same resource abstractions, but DCP is not a Kubernetes distribution — it’s a lightweight local runtime.
Everything in Aspire is a resource. The resource model is hierarchical:
IResource (interface)
└── Resource (abstract base)
├── ProjectResource — .NET project reference
├── ContainerResource — Docker/OCI container
├── ExecutableResource — Native process (polyglot apps)
├── ParameterResource — Config value or secret
└── Infrastructure resources
├── RedisResource
├── PostgresServerResource
├── MongoDBServerResource
├── SqlServerResource
├── RabbitMQServerResource
├── KafkaServerResource
└── ... (one per integration)Every resource has:
Annotations are metadata bags attached to resources. Common built-in annotations:
| Annotation | Purpose |
|---|---|
EndpointAnnotation | Defines an HTTP/HTTPS/TCP endpoint |
EnvironmentCallbackAnnotation | Deferred env var resolution |
HealthCheckAnnotation | Health check configuration |
ContainerImageAnnotation | Docker image details |
VolumeAnnotation | Volume mount configuration |
CommandLineArgsCallbackAnnotation | Dynamic CLI arguments |
ManifestPublishingCallbackAnnotation | Custom publish behavior |
NotStarted → Starting → Running → Stopping → Stopped
↓ ↓
FailedToStart RuntimeUnhealthy
↓
Restarting → RunningResources form a dependency graph. Aspire starts resources in topological order:
PostgreSQL ──→ API ──→ Frontend
Redis ────────↗
RabbitMQ ──→ Worker.WaitFor() adds a health-check gate to the dependency edge. Without it, the dependency starts but the downstream doesn’t wait for health.
Aspire injects environment variables into each resource so services can find each other. No service registry or DNS is needed — it’s pure environment variable injection.
For databases, caches, and message brokers:
ConnectionStrings__<resource-name>=<connection-string>Examples:
ConnectionStrings__cache=localhost:6379
ConnectionStrings__catalog=Host=localhost;Port=5432;Database=catalog;Username=postgres;Password=...
ConnectionStrings__messaging=amqp://guest:guest@localhost:5672For HTTP/HTTPS services:
services__<resource-name>__<scheme>__0=<url>Examples:
services__api__http__0=http://localhost:5234
services__api__https__0=https://localhost:7234
services__ml__http__0=http://localhost:8000var redis = builder.AddRedis("cache");
var api = builder.AddProject<Projects.Api>("api")
.WithReference(redis);This does:
ConnectionStrings__cache=localhost:<auto-port> to the API’s environmentbuilder.Configuration.GetConnectionString("cache") returns the connection stringAll languages use the same env var pattern:
| Language | How to read |
|---|---|
| C# | builder.Configuration.GetConnectionString("cache") |
| Python | os.environ["ConnectionStrings__cache"] |
| JavaScript | process.env.ConnectionStrings__cache |
| Go | os.Getenv("ConnectionStrings__cache") |
| Java | System.getenv("ConnectionStrings__cache") |
| Rust | std::env::var("ConnectionStrings__cache") |
In aspire run mode, DCP runs a reverse proxy for each exposed endpoint:
Browser → Proxy (auto-assigned port) → Actual Service (target port)// Let DCP auto-assign the external port, service listens on 8000
builder.AddPythonApp("ml", "../ml", "main.py")
.WithHttpEndpoint(targetPort: 8000);
// Fix the external port to 3000
builder.AddViteApp("web", "../frontend")
.WithHttpEndpoint(port: 3000, targetPort: 5173);// HTTP endpoint
.WithHttpEndpoint(port?, targetPort?, name?)
// HTTPS endpoint
.WithHttpsEndpoint(port?, targetPort?, name?)
// Generic endpoint (TCP, custom schemes)
.WithEndpoint(port?, targetPort?, scheme?, name?, isExternal?)
// Mark endpoints as externally accessible (for deployment)
.WithExternalHttpEndpoints()Aspire configures OpenTelemetry automatically for .NET services. For non-.NET services, you configure OpenTelemetry manually, pointing at the DCP collector.
The DCP exposes an OTLP endpoint. Set these env vars in your non-.NET service:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_SERVICE_NAME=<your-service-name>Aspire auto-injects OTEL_EXPORTER_OTLP_ENDPOINT via .WithReference() for the dashboard collector.
The ServiceDefaults project is a shared configuration library that standardizes:
/health, /alive)// In each .NET service's Program.cs
builder.AddServiceDefaults(); // adds OTel, health checks, resilience
// ... other service config ...
app.MapDefaultEndpoints(); // maps /health and /aliveEvery integration adds health checks automatically on the client side:
PING commandSELECT 1ping command// WithReference: wires connection string + creates dependency edge
// (downstream may start before dependency is healthy)
.WithReference(db)
// WaitFor: gates on health check — downstream won't start until healthy
.WaitFor(db)
// Typical pattern: both
.WithReference(db).WaitFor(db)var api = builder.AddProject<Projects.Api>("api")
.WithHealthCheck("ready", "/health/ready")
.WithHealthCheck("live", "/health/live");The AppHost supports lifecycle events for reacting to resource state changes:
builder.Eventing.Subscribe<ResourceReadyEvent>("api", (evt, ct) =>
{
// Fires when "api" resource becomes healthy
Console.WriteLine($"API is ready at {evt.Resource.Name}");
return Task.CompletedTask;
});
builder.Eventing.Subscribe<BeforeResourceStartedEvent>("db", async (evt, ct) =>
{
// Run database migrations before the DB resource is marked as started
await RunMigrations();
});| Event | When |
|---|---|
BeforeResourceStartedEvent | Before a resource starts |
ResourceReadyEvent | Resource is healthy and ready |
ResourceStateChangedEvent | Any state transition |
BeforeStartEvent | Before the entire application starts |
AfterEndpointsAllocatedEvent | After all ports are assigned |
// Plain parameter
var apiKey = builder.AddParameter("api-key");
// Secret parameter (prompted at run, not logged)
var dbPassword = builder.AddParameter("db-password", secret: true);
// Use in resources
var api = builder.AddProject<Projects.Api>("api")
.WithEnvironment("API_KEY", apiKey);
var db = builder.AddPostgres("db", password: dbPassword);Parameters are resolved from (in priority order):
dotnet user-secrets)appsettings.json / appsettings.{Environment}.jsonaspire run)