Running our database in Aspire
One of the coolest things about Aspire is that we can run our entire system with it. The first step for us is to migrate our Postgres database to Aspire.
Adding the integration
One of the core pillars of Aspire is called an "Integration". Integrations are just Nuget packages with a fancy name. Fundamentally they just wrap existing packages with a log of orchestration and pre-configuration by Microsoft to give a great developer experience.
Let's add Postgres as a hosted integration.
First let's add the Nuget package for the integration to the AppHost project:
dotnet add package Aspire.Hosting.PostgreSQL
Then we need to configure some parameters for the project such as the database username and password:
"Parameters" :
{
"postgres-username": "workshop",
"postgres-password": "changeme"
}
Then we can configure the database integration in the Program.cs:
var mainDbUsername = builder.AddParameter("postgres-username");
var mainDbPassword = builder.AddParameter("postgres-password");
var mainDb = builder.AddPostgres("main-db", mainDbUsername, mainDbPassword, port: 5432)
.WithDataVolume()
.AddDatabase("dometrain");
builder.AddProject<Projects.Dometrain_Monolith_Api>("dometrain-api")
.WithReference(mainDb)
.WaitFor(mainDb);
Understanding WaitFor
Notice the .WaitFor(mainDb) call after the reference. This is important because it ensures that:
- The API won't start until the database is fully ready
- The database container is running
- The database is accepting connections
- Any initialization (like database creation) is complete
Without WaitFor, your API might start before the database is ready, leading to connection failures on startup.
Database Creation
Good news! The .AddDatabase("dometrain") call now automatically creates the database if it doesn't exist. You no longer need to manually create the database or write initialization code to handle this. Aspire takes care of it for you.
At this point, nothing needs to change on our API level. Simply run the AppHost project and see the magic happen. We now have a new database that's being run and managed by Aspire.

There are however benefits in migrating our current code to use the Aspire Postgres client, mainly around resilience and telemetry. Thankfully it's very simple to do.
First install the Postgres Aspire package in the API project.
dotnet add package Aspire.Npgsql
Then simply call the dependency injection AddNpgsqlDataSource method in the API's Program.cs and replace
the multiplexer registration code.
builder.Services.AddSingleton<IDbConnectionFactory, NpgsqlConnectionFactory>();
builder.AddNpgsqlDataSource("dometrain");
Lastly we need to update the NpgsqlConnectionFactory to use the new NpgsqlDataSource.
public class NpgsqlConnectionFactory : IDbConnectionFactory
{
private readonly NpgsqlDataSource _dataSource;
public NpgsqlConnectionFactory(NpgsqlDataSource dataSource)
{
_dataSource = dataSource;
}
public async Task<IDbConnection> CreateConnectionAsync(CancellationToken token = default)
{
var connection = await _dataSource.OpenConnectionAsync(token);
return connection;
}
}
Container Lifetime Management
By default, containers managed by Aspire are ephemeral—they're created when you start the AppHost and destroyed when you stop it. This means:
- Fresh containers on every run
- Data is lost between runs (unless using volumes)
- Startup time includes container creation and initialization
Persistent Containers
To avoid long startup times and keep containers running between Aspire runs, you can use WithLifetime(ResourceLifetime.Persistent):
var mainDb = builder.AddPostgres("main-db", mainDbUsername, mainDbPassword, port: 5432)
.WithDataVolume()
.WithLifetime(ResourceLifetime.Persistent)
.AddDatabase("dometrain");
With persistent lifetime:
- Containers stay alive between AppHost runs
- Faster startup times - no need to recreate and initialize containers
- Data persists across development sessions
- Containers are only created once and reused
This is especially useful for:
- Databases with seed data
- Services that take time to initialize
- Development environments where you want consistent state
When to Use Persistent vs. Ephemeral
Use Persistent (WithLifetime(ResourceLifetime.Persistent)) when:
- You want faster startup times during development
- The resource takes a long time to initialize
- You need data to persist between runs (combined with
.WithDataVolume()) - You're working on application code, not infrastructure
Use Ephemeral (default) when:
- You want a clean slate on every run
- Testing requires fresh state
- You're debugging infrastructure setup
- You want to ensure your initialization code works correctly
Note: Even with persistent containers, using .WithDataVolume() is still recommended to ensure database data persists on disk, protecting against accidental container removal.
Next Steps
Now that we have our entire system running through Aspire with proper database management, let's take a look at how we can expand our tech choices even further.