.NET Aspire, now branded simply as Aspire, is Microsoft's code-first toolchain for building, running and deploying distributed applications. It replaces scattered compose files, launch scripts and copied connection strings with a C# AppHost that describes every service, database and cloud resource in your system. This guide targets Aspire 13.5, the current release in September 2026. It covers the resource model, service defaults, the dashboard, integrations, the Aspire CLI, testing and the deployment paths that take one model to Azure, Docker Compose and Kubernetes.

What Is .NET Aspire?#

Aspire is an opinionated stack for cloud-native .NET development, and more recently for polyglot development too. It gives you three things:

  • Orchestration. A small program called the AppHost declares your application as a graph of resources and runs the whole graph locally with one command.
  • Integrations. Curated NuGet packages model infrastructure such as PostgreSQL, Redis, Kafka or Azure Service Bus in the AppHost. Matching client packages wire the SDKs into your services with health checks, telemetry and resilience already configured.
  • Tooling. The Aspire CLI, a dashboard for logs, traces and metrics, a test host and a deployment pipeline that turns the same model into Bicep, Docker Compose files or Helm charts.

Aspire 13.0, released on November 11, 2025, dropped the ".NET" prefix because Python and JavaScript apps became first-class citizens, and version numbers jumped from 9.x straight to 13. Later minor releases added a TypeScript AppHost, now generally available, plus Go and Bun integrations. Aspire 13.5 shipped on August 18, 2026, and the latest patch at the time of writing is 13.5.4.

The Aspire CLI and AppHost SDK require the .NET 10 SDK, but the projects you orchestrate can target .NET 8, .NET 9, .NET 10 or a .NET 11 preview. The support policy is strict: only the latest Aspire release is supported, and a minor version loses support when the next one ships. Plan to upgrade Aspire every few months, even while your services stay on an LTS runtime.

Aspire is not a runtime framework, a service mesh or a hosting platform. Your services reference ordinary libraries and run fine without the AppHost, and production traffic never flows through it.

How Aspire Works: The AppHost and the Resource Model#

The AppHost is a console program built on the Aspire.AppHost.Sdk. It calls DistributedApplication.CreateBuilder, adds resources and then calls Build().Run(). Each resource is a node in the application model. Aspire ships resource types for .NET projects, containers, executables, parameters, connection strings, cloud resources and language-specific apps such as Vite, Node.js, Python and Go.

Resources carry annotations that describe endpoints, environment variables, volumes, health checks and relationships. Three relationships do most of the work:

  • WithReference injects connection information into a consumer. A database reference becomes a ConnectionStrings__catalogdb variable, and a project reference becomes service discovery configuration. Since Aspire 13, references also expose individual connection properties such as CATALOGDB_URI or CATALOGDB_HOST, which suits Python, Node.js and Java consumers that do not understand .NET connection strings.
  • WaitFor delays a resource until its dependency is running and healthy. WaitForCompletion waits until a dependency exits successfully, which is ideal for database migration jobs.
  • WithExternalHttpEndpoints marks endpoints that should be reachable from outside the environment when you deploy.

The same model runs in two modes. In run mode (aspire run), the local orchestrator starts processes and containers, allocates ports, injects configuration and streams telemetry to the dashboard. In publish mode (aspire publish and aspire deploy), pipeline steps registered by compute environments translate the model into deployment artifacts. Branch on builder.ExecutionContext.IsPublishMode when the modes need different resources.

Service discovery ties the pieces together. When service A references service B, the AppHost injects endpoint configuration, and the discovery library resolves logical URIs such as https+http://catalog-api at runtime, preferring HTTPS and falling back to HTTP. Discovery configuration exists only for resources you reference explicitly, which keeps coupling visible in the AppHost.

Getting Started with Aspire 13#

You need the .NET 10 SDK and a container runtime such as Docker Desktop or Podman. Install the CLI with the official script or as a .NET tool. Aspire 13.5 also distributes it through npm, Homebrew, WinGet and other package managers.

Bash
# Install the Aspire CLI with the script...
curl -sSL https://aspire.dev/install.sh | bash
# ...or as a .NET global tool
dotnet tool install -g Aspire.Cli

# Create a starter solution: AppHost, ServiceDefaults, API and Blazor front end
aspire new aspire-starter --name Shop --output ./shop
cd shop

# Build and start every resource, then open the dashboard URL printed in the terminal
aspire run

The generated AppHost is short. The version below is a realistic evolution of it, with PostgreSQL, Redis, a migration worker and two services.

C#
var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
    .WithDataVolume()
    .WithLifetime(ContainerLifetime.Persistent)
    .WithPgAdmin();

var catalogDb = postgres.AddDatabase("catalogdb");

var cache = builder.AddRedis("cache")
    .WithLifetime(ContainerLifetime.Persistent);

// Runs EF Core migrations once, then exits
var migrations = builder.AddProject<Projects.Shop_MigrationService>("migrations")
    .WithReference(catalogDb)
    .WaitFor(catalogDb);

var api = builder.AddProject<Projects.Shop_CatalogApi>("catalog-api")
    .WithReference(catalogDb)
    .WithReference(cache)
    .WaitFor(cache)
    .WaitForCompletion(migrations)
    .WithHttpHealthCheck("/health");

builder.AddProject<Projects.Shop_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(api)
    .WaitFor(api);

builder.Build().Run();

Persistent containers with data volumes survive AppHost restarts, so the inner loop does not pay for a fresh database on every run. The Projects.* types are generated from the AppHost's project references, which keeps the model strongly typed. For small or polyglot repositories, Aspire 13 also supports a single-file AppHost with #:package directives and projects referenced by path:

C#
#:sdk Aspire.AppHost.Sdk@13.5.4
#:package Aspire.Hosting.Redis@13.5.4

var builder = DistributedApplication.CreateBuilder(args);

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

builder.AddProject("api", "../src/Api/Api.csproj")
    .WithReference(cache)
    .WaitFor(cache);

builder.Build().Run();

Service Defaults: OpenTelemetry, Health Checks, Resilience and Service Discovery#

Every service in an Aspire solution references a shared ServiceDefaults project. It is plain source code that you own, not a black-box package, and it exposes two extension methods: AddServiceDefaults on the host builder and MapDefaultEndpoints on the web application.

C#
var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Client integrations: connection names match the AppHost resource names
builder.AddNpgsqlDbContext<CatalogDbContext>("catalogdb");
builder.AddRedisDistributedCache("cache");

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();

app.MapGet("/products/{id:int}", async (int id, CatalogDbContext db, CancellationToken ct) =>
    await db.Products.FindAsync([id], ct) is { } product
        ? Results.Ok(product)
        : Results.NotFound());

app.MapDefaultEndpoints();

app.Run();

In the current template, AddServiceDefaults does four things:

  • OpenTelemetry. It enables logging with formatted messages and scopes, metrics for ASP.NET Core, HttpClient and the runtime, and tracing for ASP.NET Core and HttpClient. Health probe requests are filtered out of traces. The OTLP exporter switches on only when OTEL_EXPORTER_OTLP_ENDPOINT is set, which the AppHost does automatically in run mode.
  • Health checks. It registers a trivial self check tagged live. Client integrations add their own checks, such as a PostgreSQL connectivity check.
  • Service discovery. It registers the configuration-based endpoint resolver.
  • Resilience. ConfigureHttpClientDefaults adds the standard resilience handler (retry, circuit breaker and timeouts) and service discovery to every HttpClient the app creates.

MapDefaultEndpoints maps /health, where every check must pass, and /alive, where only checks tagged live must pass. The template maps them only in the Development environment, because health endpoints can leak information or become a denial-of-service target. For production, map them deliberately, for example on a management port that is not routed publicly:

C#
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
    // Kestrel must also listen on 8081, e.g. ASPNETCORE_HTTP_PORTS=8080;8081
    app.MapHealthChecks("/health").RequireHost("*:8081");

    app.MapHealthChecks("/alive", new HealthCheckOptions
    {
        Predicate = registration => registration.Tags.Contains("live")
    }).RequireHost("*:8081");

    return app;
}

Consumers call other services by logical name. The base address below resolves through service discovery locally and through the injected configuration after deployment:

C#
builder.Services.AddHttpClient<CatalogClient>(client =>
    client.BaseAddress = new Uri("https+http://catalog-api"));

Because the ServiceDefaults project is yours, extend it rather than working around it. Typical additions are EF Core or gRPC instrumentation, an Azure Monitor exporter that the template leaves commented out, or restricting service discovery to HTTPS through ServiceDiscoveryOptions.AllowedSchemes. For deeper coverage of the telemetry side, see the OpenTelemetry in .NET guide. For the resilience pipeline, see resilience with Polly.

The Aspire Dashboard#

aspire run starts a web dashboard that shows every resource with its state, endpoints, environment variables and health, plus console logs, structured logs, distributed traces and metrics. You can start, stop and restart resources, run custom resource commands and follow a request across services. Aspire 13.5 added sharper filtering and interactive terminal sessions through the experimental WithTerminal API.

The dashboard is an OTLP endpoint, so it also runs standalone next to any OpenTelemetry-instrumented app, including apps that do not use an AppHost:

Bash
docker run --rm -it -d --name aspire-dashboard \
  -p 18888:18888 -p 4317:18889 -p 4318:18890 \
  mcr.microsoft.com/dotnet/aspire-dashboard:latest

The UI listens on port 18888, OTLP arrives over gRPC on 4317 and HTTP on 4318, and the login token is printed to the container logs. Telemetry lives in memory with fixed limits, so the dashboard is meant for development and short-term diagnostics. In production, send OTLP to Azure Monitor, an OpenTelemetry Collector or another durable backend. For AI-assisted work, aspire agent init configures an MCP server and skill files so coding agents can list resources, read logs and inspect traces.

Aspire Integrations: Redis, PostgreSQL and Azure Services#

Integrations come in pairs. A hosting integration extends the AppHost with a resource type, such as AddPostgres from Aspire.Hosting.PostgreSQL. A client integration registers the SDK in your service, such as AddNpgsqlDbContext from Aspire.Npgsql.EntityFrameworkCore.PostgreSQL, with health checks, logging, tracing and metrics enabled by default. The pair shares only a connection name. Client integrations also work without an AppHost, provided that a connection string with that name exists in configuration.

ResourceHosting package (AppHost)Client package (service)Client registration
PostgreSQLAspire.Hosting.PostgreSQLAspire.Npgsql or Aspire.Npgsql.EntityFrameworkCore.PostgreSQLAddNpgsqlDataSource, AddNpgsqlDbContext
RedisAspire.Hosting.RedisAspire.StackExchange.Redis plus caching variantsAddRedisClient, AddRedisDistributedCache, AddRedisOutputCache
Azure Service BusAspire.Hosting.Azure.ServiceBusAspire.Azure.Messaging.ServiceBusAddAzureServiceBusClient
Azure Blob StorageAspire.Hosting.Azure.StorageAspire.Azure.Storage.BlobsAddAzureBlobServiceClient

Use aspire add postgres or aspire add redis to add hosting packages with matching versions. Azure hosting integrations follow a consistent pattern. They generate Bicep for the real Azure service when you publish, and RunAsEmulator or RunAsContainer swap in a local emulator or an open-source container for run mode:

C#
var builder = DistributedApplication.CreateBuilder(args);

var serviceBus = builder.AddAzureServiceBus("messaging")
    .RunAsEmulator();
serviceBus.AddServiceBusQueue("orders");

var blobs = builder.AddAzureStorage("storage")
    .RunAsEmulator()          // Azurite locally, a Storage account in Azure
    .AddBlobs("blobs");

var ordersDb = builder.AddAzurePostgresFlexibleServer("pg")
    .RunAsContainer()         // postgres container locally, Flexible Server in Azure
    .AddDatabase("ordersdb");

builder.AddProject<Projects.Orders_Worker>("orders-worker")
    .WithReference(serviceBus)
    .WithReference(blobs)
    .WithReference(ordersDb)
    .WaitFor(serviceBus);

builder.Build().Run();

AsExisting and PublishAsExisting reference Azure resources that already exist instead of provisioning new ones. To provision real Azure resources during local runs, store Azure:SubscriptionId and Azure:Location with aspire secret set rather than in a committed appsettings file.

Secrets belong in parameters. builder.AddParameter("stripe-key", secret: true) reads Parameters:stripe-key from environment variables, user secrets or other configuration, and the dashboard prompts for missing values. At deployment time the value is resolved or emitted as a placeholder.

The Aspire CLI#

The CLI has grown from a thin wrapper into the main interface for both people and coding agents. New C# templates set AspireUseCliBundle, so dotnet run on the AppHost delegates to aspire run.

CommandWhat it does
aspire new, aspire initCreate a solution from a template, or add Aspire to an existing repository
aspire run, aspire start, aspire stopRun the AppHost in the foreground, in the background, or stop it
aspire add, aspire updateAdd integration packages, and update packages or the CLI itself
aspire describe, aspire logs, aspire otelInspect resources, console logs and telemetry from the terminal
aspire publish, aspire deploy, aspire destroyGenerate artifacts, deploy them, or tear down a deployment
aspire doRun a specific pipeline step and its dependencies
aspire secret, aspire certs, aspire doctorManage user secrets and dev certificates, and diagnose the environment
aspire agentConfigure MCP and skills so AI coding agents can drive the app

Testing with DistributedApplicationTestingBuilder#

Aspire.Hosting.Testing starts your real AppHost, including containers, inside a test process. That makes it the closest thing to an end-to-end test you can run on a laptop or CI agent. The aspire-xunit, aspire-mstest and aspire-nunit templates set up the project. A typical xUnit v3 test looks like this:

C#
using System.Net;

namespace Shop.Tests;

public class StorefrontTests
{
    private static readonly TimeSpan StartupTimeout = TimeSpan.FromSeconds(90);

    [Fact]
    public async Task Home_page_returns_ok_once_dependencies_are_healthy()
    {
        var ct = TestContext.Current.CancellationToken;

        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.Shop_AppHost>(ct);

        appHost.Services.ConfigureHttpClientDefaults(http =>
            http.AddStandardResilienceHandler());

        await using var app = await appHost.BuildAsync(ct).WaitAsync(StartupTimeout, ct);
        await app.StartAsync(ct).WaitAsync(StartupTimeout, ct);

        await app.ResourceNotifications
            .WaitForResourceHealthyAsync("webfrontend", ct)
            .WaitAsync(StartupTimeout, ct);

        using var client = app.CreateHttpClient("webfrontend");
        using var response = await client.GetAsync("/", ct);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

CreateHttpClient resolves the resource's allocated endpoint, and GetConnectionStringAsync("catalogdb") hands you the real connection string so you can seed or assert on data directly. These tests are slower than unit tests because containers must start. Share one running app per test class through a fixture, keep the number of scenarios focused, and make sure your CI agents have a container runtime. For in-process testing of a single service, WebApplicationFactory and Testcontainers remain the better tools. The integration testing guide compares the approaches.

Deploying Aspire Apps: aspire deploy, azd, Docker Compose and Kubernetes#

Deployment starts in the AppHost. You declare one or more compute environments, and their pipeline steps translate the model into artifacts. aspire publish emits those artifacts and leaves parameters unresolved so that other tooling can apply them. aspire deploy resolves parameters, builds and pushes images and applies the changes in one step. Both commands have been generally available since Aspire 13.4.

C#
using Aspire.Hosting.Kubernetes.Resources;

var builder = DistributedApplication.CreateBuilder(args);

var aca = builder.AddAzureContainerAppEnvironment("aca");

var k8s = builder.AddKubernetesEnvironment("k8s")
    .WithHelm(helm =>
    {
        helm.WithNamespace("shop");
        helm.WithReleaseName("shop");
    });

builder.AddProject<Projects.Shop_CatalogApi>("catalog-api")
    .WithComputeEnvironment(aca)
    .PublishAsAzureContainerApp((infra, app) =>
    {
        app.Template.Scale.MinReplicas = 1;
        app.Template.Scale.MaxReplicas = 10;
    });

builder.AddProject<Projects.Shop_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithComputeEnvironment(k8s)
    .PublishAsKubernetesService(resource =>
    {
        if (resource.Workload is Deployment deployment)
        {
            deployment.Spec.Replicas = 3;
        }
    });

builder.Build().Run();

Mixing two targets in one AppHost is unusual, and the example does it only to show both APIs. Most teams declare one environment per AppHost. When several environments exist, WithComputeEnvironment binds each resource to the right one. The commands are short:

Bash
aspire publish --output-path ./artifacts   # Bicep, compose files or Helm charts
aspire deploy --environment Production     # resolve parameters, push images, apply
aspire destroy                             # tear the deployment down again
TargetAppHost APIPackage status (13.5)What you get
Azure Container AppsAddAzureContainerAppEnvironmentStableBicep, ACR images, managed identity, optional dashboard
Azure App ServiceAddAzureAppServiceEnvironmentStableBicep and App Service plans with the dashboard by default
Docker ComposeAddDockerComposeEnvironmentStabledocker-compose.yaml plus an .env file for parameters
KubernetesAddKubernetesEnvironmentPreview packageA Helm chart, installed into the current kubectl context by aspire deploy
AKSAddAzureKubernetesEnvironmentPreview packageA Helm chart plus Bicep for the Azure infrastructure

The Azure Developer CLI (azd) still works: azd up provisions and deploys to Container Apps. However, the Aspire documentation no longer recommends it as the default path, because azd consumes the legacy deployment manifest, which is deprecated, and new Azure deployment features land in aspire deploy first. Existing azd pipelines need not migrate urgently, but new work should start with aspire deploy or with aspire publish feeding your own CI/CD.

The Kubernetes hosting packages are still prereleases in 13.5, so review the generated Helm chart like any generated infrastructure. The Kubernetes production guide covers probes, resource limits and graceful shutdown. To choose between Azure targets, see App Service vs Container Apps vs AKS.

Best Practices#

  • Keep the AppHost declarative. Describe topology, not business logic. Complex setup belongs in dedicated resources, such as a migration worker, that the AppHost waits on.
  • Model every dependency with WaitFor and health checks. Startup races between services and databases are the most common local failure, and Aspire prevents them only if you declare the edges.
  • Use persistent containers and data volumes for stateful dev resources. They make aspire run fast and keep test data between sessions.
  • Treat ServiceDefaults as platform code. Version it, review changes and add organization-wide defaults such as exporters and HTTPS-only discovery in one place.
  • Expose health endpoints deliberately in production. Map them on an internal port or behind authorization, and point orchestrator probes at them.
  • Put secrets in parameters. Mark them secret: true and keep local values in user secrets.
  • Pin and update Aspire as a unit. Keep the AppHost SDK and every Aspire.* package on one version, and run aspire update when a minor release ships.

Common Pitfalls#

  • Expecting /health to exist after deployment. The template maps health endpoints only in Development, so probes that target them fail in production until you map them yourself.
  • Mismatched connection names. AddNpgsqlDbContext resolves a connection string named after the AppHost resource. A typo produces a configuration error at startup, not a compile error.
  • Hard-coding URLs and ports. Use logical names such as https+http://catalog-api. Hard-coded localhost ports break as soon as Aspire allocates different ones or the app is deployed.
  • Treating Aspire as a runtime dependency. Services should run without the AppHost when given configuration. If a service only works under aspire run, you have coupled it to development tooling.
  • Changing a database password after its volume exists. PostgreSQL applies the password only when it initializes an empty data directory, so a new parameter value produces authentication failures until you delete the volume.
  • Shipping preview integrations without review. Kubernetes and AKS support is still in preview. Pin the package versions and review the generated charts before they reach production.

When to Use Aspire: Aspire vs Docker Compose vs Scripts#

ConcernAspire AppHostDocker ComposeScripts and README steps
Definition languageC#, or TypeScript in a TypeScript AppHostYAMLShell or PowerShell
Running .NET projectsAs local processes you can debug from the IDEOnly as containersManually, one terminal per service
Connection strings and discoveryInjected by WithReferenceHand-written environment variablesCopied between files
Startup orderingWaitFor backed by health checksdepends_on with health conditionsSleep loops and retries
TelemetryBuilt-in dashboard over OTLPBring your own stackNone
End-to-end testsDistributedApplicationTestingBuilderExternal scripts or TestcontainersManual
Deployment artifactsBicep, Compose files or Helm chartsThe Compose file itselfNone

Aspire is a strong fit when you have more than two or three services, when developers struggle to run the full system locally, or when you want consistent telemetry and resilience defaults across teams. It adds little for a single API with one database. It also does not replace a platform team's GitOps pipeline; there, use aspire publish to generate artifacts that existing tooling applies.

Frequently Asked Questions#

Is .NET Aspire the same product as Aspire?#

Yes. Aspire 13.0 dropped the ".NET" prefix in November 2025 when Python and JavaScript became first-class, but it is the same project and repository. Most documentation, packages and APIs kept their names, and the C# AppHost remains the primary experience.

Do I need Aspire in production?#

No. The AppHost is a development and deployment tool, and nothing in your production request path depends on it. Your services keep the ServiceDefaults code and client integrations, which are ordinary libraries configured through standard .NET configuration.

Which .NET versions can I use with Aspire 13?#

The Aspire CLI and AppHost SDK require the .NET 10 SDK. The services you orchestrate can target .NET 8, .NET 9 or .NET 10, and templates in 13.5 can also target a .NET 11 preview. Keep production services on an LTS release unless you need a newer feature.

Should I deploy with azd or aspire deploy?#

For new projects, start with aspire deploy, which the Aspire documentation now recommends and which receives new Azure deployment features first. azd remains supported and is reasonable for existing pipelines, but it relies on the deprecated deployment manifest.

Can Aspire deploy to Kubernetes?#

Yes. AddKubernetesEnvironment generates a Helm chart, and aspire deploy installs it into your current kubectl context. The Kubernetes and AKS packages are still prereleases in Aspire 13.5, so review the generated chart and pin versions before you rely on it in production.

Summary#

  • Aspire is a code-first stack: an AppHost resource model, paired hosting and client integrations, a dashboard, a CLI and a deployment pipeline.
  • Aspire 13.5 is current. It requires the .NET 10 SDK but orchestrates services targeting .NET 8 through a .NET 11 preview, and only the latest release is supported.
  • ServiceDefaults gives every service OpenTelemetry, health checks, service discovery and HTTP resilience, with health endpoints mapped only in Development by default.
  • DistributedApplicationTestingBuilder runs the real system in tests; keep those scenarios focused.
  • aspire publish and aspire deploy target Container Apps, App Service, Docker Compose and Kubernetes, with Kubernetes still in preview.

Further Reading#