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 first 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 keyParameters: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:
.NET Aspire