.NET Aspire is an application framework for building observable, production-ready, cloud-native applications with .NET. It provides a consistent way to manage your application's dependencies, configuration, and deployment.
Think of Aspire as a layer that sits on top of your application and orchestrates how everything works together. It handles service discovery, configuration, health checks, and telemetry out of the box.
Starting from version 13, .NET Aspire was rebranded to Aspire and is now a multi-language application platform.
While Aspire continues to provide best-in-class support for .NET applications, version 13.0 brings support for Python and JavaScript. It provides comprehensive support for running, debugging, and deploying applications written in these languages.
Today, I want to show you how to build and run a .NET application with Aspire 13 and .NET 10.
In this post, we will explore:
- Getting Started with Aspire 13
- Configuring PostgreSQL and Redis in Aspire
- Exploring Aspire Dashboard
- Configuring OpenTelemetry
- Deploying Aspire Application to Docker Compose
Let's dive in.
Getting Started with Aspire 13
I have built a product API that has the following integrations:
- PostgreSQL database
- HybridCache with Redis

To add Aspire to your project, we need to do the following steps:
Step 1:
Install the Aspire project templates:
bashdotnet new install Aspire.ProjectTemplates
Step 2:
Install the Aspire CLI:
bashdotnet tool install --global aspire.cli
Step 3:
Add the Aspire support for the Products.Api project in Visual Studio or Rider:

Aspire introduces two new project types to your solution:
- AppHost: The orchestrator that defines your application's architecture and dependencies.
- ServiceDefaults: Shared configuration and observability setup applied to all services.

By default, the AppHost project has the entry file name AppHost.cs with the following content:
csharpvar builder = DistributedApplication.CreateBuilder(args); builder .AddProject<Projects.Products_Api>("products-api"); builder.Build().Run();
The only dependency declared in the AppHost project is Products.Api, which contains our Web API.
Step 4:
Install the following NuGet packages in the AppHost project:
bashdotnet add package Aspire.Hosting.PostgreSQL dotnet add package Aspire.Hosting.Redis
Step 5:
Configure all the dependencies in the AppHost.cs:
csharpvar builder = DistributedApplication.CreateBuilder(args); var cache = builder.AddRedis("cache"); var postgres = builder.AddPostgres("postgres"); builder .AddProject<Projects.Products_Api>("products-api") .WithReference(cache) .WithReference(postgres); builder.Build().Run();
The WithReference(cache) and WithReference(postgres) methods inject connection strings for Redis and PostgreSQL into the API project.
Aspire automatically sets environment variables that the API can read to connect to these resources.
Step 6:
Configure the connection strings in the Products.Api project to connect to the PostgreSQL database:
csharpusing Microsoft.EntityFrameworkCore; using Products.Domain.Products; using Products.Infrastructure.Database; using Products.Infrastructure.Repositories; namespace Microsoft.Extensions.DependencyInjection; public static class HostDiExtensions { private static IServiceCollection AddEfCore( this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("postgres"); services.AddDbContext<ProductDbContext>((_, options) => { options.UseNpgsql(connectionString); }); return services; } }
The key part is configuration.GetConnectionString("postgres"). This reads the connection string that Aspire injected when we called WithReference(postgres) in AppHost.cs.
Aspire automatically sets an environment variable with the PostgreSQL connection string, and the configuration system reads it.
This way, you don't need to hardcode connection strings in appsettings.json or your own environment variables.
In this project, I use the regular EF Core NuGet packages for Npgsql:
Microsoft.EntityFrameworkCore10.0.0Microsoft.EntityFrameworkCore.Design10.0.0Npgsql.EntityFrameworkCore.PostgreSQL10.0.0
Step 7:
Configure the connection string in the Products.Api project to connect to the Redis cache:
csharppublic static IServiceCollection AddWebHostInfrastructure( this IServiceCollection services, IConfiguration configuration) { services.AddHybridCache(options => { options.DefaultEntryOptions = new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(10), LocalCacheExpiration = TimeSpan.FromMinutes(10) }; }); return services; } private static IServiceCollection AddRedis( this IServiceCollection services, IConfiguration configuration) { var redisConnectionString = configuration.GetConnectionString("cache")!; services.AddMemoryCache(); services .AddStackExchangeRedisCache(options => { options.Configuration = redisConnectionString; }); return services; }
The AddHybridCache method registers HybridCache with default expiration settings.
The AddRedis method registers StackExchangeRedisCache as the L2 cache.
Just like with PostgreSQL, Aspire injects the Redis connection string via configuration.GetConnectionString("cache").
In this project, I use the following NuGet packages for Redis cache:
Microsoft.Extensions.Caching.Hybrid10.1.0Microsoft.Extensions.Caching.StackExchangeRedis10.0.1
Step 8:
Let's run our application locally with Visual Studio or Rider:

Aspire dashboard
The Aspire dashboard opens automatically in your web browser (the port may vary). The dashboard shows:
- Resources - All running services (postgres, cache, products-api)
- Console logs - Real-time logs from each service
- Structured logs - Filterable and searchable logs
- Traces - Distributed traces across services
- Metrics - Performance metrics and health checks
The first run may take a few minutes while Docker pulls the container images. Subsequent runs will be much faster.
The dashboard shows all running services, their health status, logs, and distributed traces.

Configuring PostgreSQL and Redis in Aspire
In Aspire, you can easily configure PostgreSQL and Redis resources with available options.
Redis resource with RedisInsight
csharpvar cache = builder.AddRedis("cache") .WithRedisInsight();
This declares a Redis resource named "cache". The WithRedisInsight() method adds RedisInsight, a web-based GUI for inspecting Redis data.
When you run the app, Aspire starts a Redis container and a RedisInsight container. You can browse to RedisInsight from the Aspire dashboard to view cached keys and values.

PostgreSQL resource with pgAdmin
csharpvar postgres = builder.AddPostgres("postgres") .WithPgAdmin() .WithDataVolume(isReadOnly: false);
This declares a PostgreSQL resource named "postgres".
The WithPgAdmin() method adds pgAdmin, a web-based database management tool.
The WithDataVolume(isReadOnly: false) method creates a Docker volume to persist database data across container restarts.
We can use pgAdmin to explore the database and run queries:

To access RedisInsight and pgAdmin, you can click on the corresponding links in the Aspire dashboard:

Explore the official documentation for more customization options:
Exploring Aspire Dashboard
Here is what the Aspire dashboard looks like after running the application locally:

How Aspire wires everything together
When you run the AppHost, Aspire does the following:
- Starts the PostgreSQL container and pgAdmin container
- Starts the Redis container and RedisInsight container
- Waits for all containers to be healthy
- Injects connection strings into the Products.Api project via environment variables
- Starts the Products.Api project
- Opens the Aspire dashboard in your browser
Here you can manage containers: start, stop, and view their logs.
You can also switch to the Graph view to see the dependencies between services:

On the Console tab you can see the logs from each service. You can view all the console logs or select a specific service to filter them:

On the Structured Logs tab you can filter and search structured logs:

Before we explore the Traces tab, let's configure OpenTelemetry in our solution.
Configuring OpenTelemetry
The ServiceDefaults project defines the observability configuration for all services.
This project is automatically added to the Products.Api project when adding the Aspire support for the solution:
csharpvar builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults();
Here is how OpenTelemetry is configured in the ServiceDefaults project:
csharppublic static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.Logging.AddOpenTelemetry(logging => { logging.IncludeFormattedMessage = true; logging.IncludeScopes = true; }); builder.Services.AddOpenTelemetry() .WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation(); }) .WithTracing(tracing => { tracing.AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation(); }); builder.AddOpenTelemetryExporters(); return builder; }
By default, Aspire configures OpenTelemetry to send logs and metrics to the dashboard. But you can configure to send logs and traces to any other destination, such as Jaeger, Seq, Grafana or any other observability tool.
By default, OpenTelemetry is configured with AspNetCore and HttpClient instrumentation.
For our project, we need to add instrumentation for the PostgreSQL and Redis integrations.
We need to install the following NuGet packages:
bashdotnet add package Npgsql.OpenTelemetry dotnet add package OpenTelemetry.Instrumentation.StackExchangeRedis
Note:
OpenTelemetry.Instrumentation.StackExchangeRedispackage is still in the preview
Next, we need to register the instrumentation from the installed packages:
csharptracing.AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddNpgsql() .AddRedisInstrumentation();
Here is what the Traces tab looks like in the Aspire dashboard:

Deploying Aspire Application to Docker Compose
What I like about the Aspire is how easy it is to deploy your application to various locations. Let's explore how we can deploy our app to Docker Compose.
First, we need to install the following NuGet package into the AppHost project:
bashdotnet add package Aspire.Hosting.Docker
Second, we need to add the following code to the AppHost.cs:
csharpbuilder.AddDockerComposeEnvironment("env"); builder .AddProject<Projects.Products_Api>("products-api") .WithExternalHttpEndpoints() .WithReference(cache) .WithReference(postgres);
AddDockerComposeEnvironment tells Aspire to use a Docker Compose environment named "env".
The WithExternalHttpEndpoints() method makes the API accessible from outside the Aspire environment.
This is needed to run the application in Docker from the created Docker Compose file.
With a single Aspire CLI command, we can deploy our application to a Docker Compose YAML file:
bashaspire publish -o docker-compose-artifacts
As a result, Aspire will generate two files in the docker-compose-artifacts folder:
.env- Environment variables for the Docker Compose environmentdocker-compose.yaml- Docker Compose file that defines the services and their dependencies
Here is what the generated docker-compose.yaml file looks like:
ymlservices: env-dashboard: image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest" expose: - "18888" - "18889" networks: - "aspire" restart: "always" cache: image: "docker.io/library/redis:8.2" command: - "-c" - "redis-server --requirepass $$REDIS_PASSWORD" entrypoint: - "/bin/sh" environment: REDIS_PASSWORD: "${CACHE_PASSWORD}" expose: - "6379" networks: - "aspire" postgres: image: "docker.io/library/postgres:17.6" environment: POSTGRES_HOST_AUTH_METHOD: "scram-sha-256" POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256" POSTGRES_USER: "postgres" POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" expose: - "5432" volumes: - type: "volume" target: "/var/lib/postgresql/data" source: "dotnetaspire13.apphost-878e181760-postgres-data" read_only: false networks: - "aspire" products-api: image: "${PRODUCTS_API_IMAGE}" environment: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" HTTP_PORTS: "${PRODUCTS_API_PORT}" ConnectionStrings__cache: "cache:6379,password=${CACHE_PASSWORD}" CACHE_HOST: "cache" CACHE_PORT: "6379" CACHE_PASSWORD: "${CACHE_PASSWORD}" CACHE_URI: "redis://:${CACHE_PASSWORD}@cache:6379" ConnectionStrings__postgres: "Host=postgres;Port=5432;Username=postgres;Password=${POSTGRES_PASSWORD}" POSTGRES_HOST: "postgres" POSTGRES_PORT: "5432" POSTGRES_USERNAME: "postgres" POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" POSTGRES_URI: "postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432" POSTGRES_JDBCCONNECTIONSTRING: "jdbc:postgresql://postgres:5432" OTEL_EXPORTER_OTLP_ENDPOINT: "http://env-dashboard:18889" OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" OTEL_SERVICE_NAME: "products-api" ports: - "${PRODUCTS_API_PORT}" networks: - "aspire" networks: aspire: driver: "bridge" volumes: dotnetaspire13.apphost-878e181760-postgres-data: driver: "local"
So much work is done for us in a single command. This is really cool!
You can find more information on Aspire apps deployment in the Aspire documentation.
Summary
Here are the key benefits of using Aspire:
- Simplified local development: Run all your services, databases, and dependencies with a single command.
- Built-in observability: OpenTelemetry integration for logs, metrics, and traces comes preconfigured.
- Service discovery: Services automatically discover each other without hardcoded URLs.
- Configuration management: Centralized configuration that works locally and in the cloud.
- Easy deployment to Docker Compose and Cloud: Deploy your entire application stack to Docker, Azure or AWS with minimal setup.
- Consistent developer experience: New team members can get the entire application running in minutes.
Aspire 13 makes it easy to deploy your application to Azure.
You can use the azd (Azure Developer CLI) tool to provision Azure resources and deploy your app to Azure Container Apps.
Aspire automatically generates the necessary infrastructure-as-code files (Bicep templates) based on your AppHost configuration.
Simply run azd init to initialize the Azure deployment configuration, then azd up to provision resources and deploy your application.
Aspire will create Azure Container Apps for your API, Azure Database for PostgreSQL, and Azure Cache for Redis, all wired together with the same connection strings and service discovery you used locally.
If you want to learn more about how to deploy your .NET applications to Azure, I recommend reading this article.
Hope you find this newsletter useful. See you next time.


Comments