Persist .NET Aspire project data using volumes

In this article, you learn how to configure .NET Aspire projects to persist data across app launches using volumes. A continuous set of data during local development is useful in many scenarios. Various .NET Aspire resource container types are able to leverage volume storage, such as PostgreSQL, Redis and Azure Storage.

When to use volumes

By default, every time you start and stop a .NET Aspire project, the app also creates and destroys the app resource containers. This setup creates problems when you want to persist data in a database or storage services between app launches for testing or debugging. For example, you may want to handle the following scenarios:

  • Work with a continuous set of data in a database during an extended development session.
  • Test or debug a changing set of files in an Azure Blob Storage emulator.
  • Maintain cached data or messages in a Redis instance across app launches.

These goals can all be accomplished using volumes. With volumes, you decide which services retain data between launches of your .NET Aspire project.

Understand volumes

Volumes are the recommended way to persist data generated by containers and supported on both Windows and Linux. Volumes can store data from multiple containers at a time, offer high performance and are easy to back up or migrate. With .NET Aspire, you configure a volume for each resource container using the ContainerResourceBuilderExtensions.WithBindMount method, which accepts three parameters:

  • Source: The source path of the volume, which is the physical location on the host.
  • Target: The target path in the container of the data you want to persist.

For the remainder of this article, imagine that your exploring a Program class in a .NET Aspire app host project that's already defined the distributed app builder bits:

var builder = DistributedApplication.CreateBuilder(args);

// TODO:
//   Consider various code snippets for configuring 
//   volumes here and persistent passwords.

builder.Build().Run();

The firsts code snippet to consider uses the WithBindMount API to configure a volume for a SQL Server resource. The following code demonstrates how to configure a volume for a SQL Server resource in a .NET Aspire app host project:

var sql = builder.AddSqlServer("sql")
                 .WithBindMount("VolumeMount.AppHost-sql-data", "/var/opt/mssql")
                 .AddDatabase("sqldb");

In this example:

  • VolumeMount.AppHost-sql-data sets where the volume will be stored on the host.
  • /var/opt/mssql sets the path to the database files in the container.

All .NET Aspire container resources can utilize volume mounts, and some provide convenient APIs for adding named volumes derived from resources. Using the WithDataVolume as an example, the following code is functionally equivalent to the previous example but more succinct:

var sql = builder.AddSqlServer("sql")
                 .WithDataVolume()
                 .AddDatabase("sqldb");

With the app host project being named VolumeMount.AppHost, the WithDataVolume method automatically creates a named volume as VolumeMount.AppHost-sql-data and is mounted to the /var/opt/mssql path in the SQL Server container. The naming convention is as follows:

  • {appHostProjectName}-{resourceName}-data: The volume name is derived from the app host project name and the resource name.

Create a persistent password

Named volumes require a consistent password between app launches. .NET Aspire conveniently provides random password generation functionality. Consider the previous example once more, where a password is generated automatically:

var sql = builder.AddSqlServer("sql")
                 .WithDataVolume()
                 .AddDatabase("sqldb");

Since the password parameter isn't provided when calling AddSqlServer, .NET Aspire automatically generates a password for the SQL Server resource.

Important

This isn't a persistent password! Instead, it changes every time the app host runs.

To create a persistent password, you must override the generated password. To do this, run the following command in your app host project directory to set a local password in your .NET user secrets:

dotnet user-secrets set Parameters:sql-password <password>

The naming convention for these secrets is important to understand. The password is stored in configuration with the Parameters:sql-password key. The naming convention follows this pattern:

  • Parameters:{resourceName}-password: In the case of the SQL Server resource (which was named "sql"), the password is stored in the configuration with the key Parameters:sql-password.

The same pattern applies to the other server-based resource types, such as those shown in the following table:

Resource type Hosting package Example resource name Override key
MySQL Aspire.Hosting.MySql mysql Parameters:mysql-password
Oracle Aspire.Hosting.Oracle oracle Parameters:oracle-password
PostgreSQL Aspire.Hosting.PostgreSQL postgresql Parameters:postgresql-password
RabbitMQ Aspire.Hosting.RabbitMq rabbitmq Parameters:rabbitmq-password
SQL Server Aspire.Hosting.SqlServer sql Parameters:sql-password

By overriding the generated password, you can ensure that the password remains consistent between app launches, thus creating a persistent password. An alternative approach is to use the AddParameter method to create a parameter that can be used as a password. The following code demonstrates how to create a persistent password for a SQL Server resource:

var sqlPassword = builder.AddParameter("sql-password", secret: true);

var sql = builder.AddSqlServer("sql", password: sqlPassword)
                 .WithDataVolume()
                 .AddDatabase("sqldb");

The preceding code snippet demonstrates how to create a persistent password for a SQL Server resource. The AddParameter method is used to create a parameter named sql-password that's considered a secret. The AddSqlServer method is then called with the password parameter to set the password for the SQL Server resource. For more information, see External parameters.

Next steps

You can apply the volume concepts in the preceding code to a variety of services, including seeding a database with data that will persist across app launches. Try combining these techniques with the resource implementations demonstrated in the following tutorials: