# Persist Aspire data with volumes and binds

Every time you start and stop an Aspire project, the app also creates and destroys the app resource containers. Any data or files stored in those containers during a debugging session is lost for subsequent sessions. Many development teams prefer to keep this data across debugging sessions so that, for example, they don't have to repopulate a database with sample data for each run.

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

## When to persist project data

Suppose you have an Aspire solution with a database resource. By default, data is saved in the container for that resource. Because all the resource containers are destroyed when you stop your app, you lose that data and won't see it the next time you run the solution. 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:

- Work with a continuous set of data in a database during an extended development session across multiple restarts.
- 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.

You can accomplish these goals using volumes or bind mounts. These objects store data outside the container in a directory on the container host, so it's not destroyed with the container. This way, you decide which services retain data between launches of your Aspire project.
**Note:** Volumes and bind mounts are features of your container runtime: Docker or
  Podman. Aspire includes methods that make it easy to work with those features.

## Compare volumes and bind mounts

Both volumes and bind mounts store data in a directory on the container host. Because this directory is outside the container, data isn't destroyed when the container stops. Volumes and bind mounts, however behave differently:

- **Volumes**: The container runtime creates and controls volumes. Volumes are isolated from the core functionality of the container host.
- **Bind mounts**: The container runtime mounts a file or directory on the host machine. Both the container and the host machine can access the contents of the bind mount.

Volumes are more secure and portable than bind mounts. They also perform better and you should use them wherever possible. Use bind mounts only if you need to access or modify the data from your host machine.

## Use volumes

Volumes are the recommended way to persist data generated by containers and they're 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 Aspire, you configure a volume for each resource container using the `WithVolume` method, which accepts three parameters:

- **`name`**: An optional name for the volume.
- **`target`**: The target path in the container of the data you want to persist.
- **`isReadOnly`**: A Boolean flag that indicates whether the data in the volume can be changed. The default value is `false`.

For the remainder of this article, imagine that you're exploring a `Program` class in an Aspire [AppHost project](/get-started/app-host/) that's already defined the distributed app builder bits:

```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

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

builder.Build().Run();
```

```typescript title="TypeScript — apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

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

await builder.build().run();
```

The first code snippet to consider uses the `WithVolume` 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 an Aspire AppHost project:

```csharp title="C# — AppHost.cs"
var sql = builder.AddSqlServer("sql")
                 .WithVolume(target: "/var/opt/mssql")
                 .AddDatabase("sqldb");
```

```typescript title="TypeScript — apphost.mts"
const sql = await builder.addSqlServer("sql");
await sql.withVolume("/var/opt/mssql");
const sqldb = await sql.addDatabase("sqldb");
```

In this example `/var/opt/mssql` sets the path to the database files in the container.

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

```csharp title="C# — AppHost.cs"
var sql = builder.AddSqlServer("sql")
                 .WithDataVolume()
                 .AddDatabase("sqldb");
```

```typescript title="TypeScript — apphost.mts"
const sql = await builder.addSqlServer("sql");
await sql.withDataVolume();
const sqldb = await sql.addDatabase("sqldb");
```

With the AppHost 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 AppHost project name and the resource name.

## Use bind mounts

Bind mounts enable access to the data from both within the container and from processes on the host machine. For example, once a bind mount is established, you can copy a file into it on your host computer. The file is then available at the bound path within the container for your resource. With Aspire, you configure a bind mount for each resource container using the `WithBindMount` method, which accepts three parameters:

- **`source`**: The path to the folder on the host machine to mount in the container.
- **`target`**: The target path in the container for the folder.
- **`isReadOnly`**: A Boolean flag that indicates whether the data in the bind mount can be changed. The default value is `false`.

Consider this code snippet, which uses the `WithBindMount` API to configure a bind mount for a SQL Server resource:

```csharp title="C# — AppHost.cs"
var sql = builder.AddSqlServer("sql")
                 .WithBindMount(source: @"C:\SqlServer\Data", target: "/var/opt/mssql")
                 .AddDatabase("sqldb");
```

```typescript title="TypeScript — apphost.mts"
const sql = await builder.addSqlServer("sql");
await sql.withBindMount("C:\\SqlServer\\Data", "/var/opt/mssql");
const sqldb = await sql.addDatabase("sqldb");
```

In this example:

- `source: @"C:\SqlServer\Data"` sets the folder on the host computer that will be bound.
- `target: "/var/opt/mssql"` sets the path to the database files in the container.

As for volumes, some Aspire container resources provide convenient APIs for adding bind mounts. Using the `WithDataBindMount` method as an example, the following code is functionally equivalent to the previous example but more succinct:

```csharp title="C# — AppHost.cs"
var sql = builder.AddSqlServer("sql")
                 .WithDataBindMount(source: @"C:\SqlServer\Data")
                 .AddDatabase("sqldb");
```

```typescript title="TypeScript — apphost.mts"
const sql = await builder.addSqlServer("sql");
await sql.withDataBindMount("C:\\SqlServer\\Data");
const sqldb = await sql.addDatabase("sqldb");
```

## Create persistent passwords

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

```csharp title="C# — AppHost.cs"
var sql = builder.AddSqlServer("sql")
                 .WithDataVolume()
                 .AddDatabase("sqldb");
```

```typescript title="TypeScript — apphost.mts"
const sql = await builder.addSqlServer("sql");
await sql.withDataVolume();
const sqldb = await sql.addDatabase("sqldb");
```

Since the `password` parameter isn't provided when calling `AddSqlServer`, Aspire automatically generates a password for the SQL Server resource.
**Caution:** This isn't a persistent password! Instead, it changes every time the AppHost
  runs.

To create a _persistent_ password, you must override the generated password. To do this, run the following command in your AppHost project directory to set a local secret:

```bash title="Aspire CLI"
aspire secret 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](https://www.nuget.org/packages/Aspire.Hosting.MySql)           | `mysql`               | `Parameters:mysql-password`      |
| Oracle        | [📦 Aspire.Hosting.Oracle](https://www.nuget.org/packages/Aspire.Hosting.Oracle)         | `oracle`              | `Parameters:oracle-password`     |
| PostgreSQL    | [📦 Aspire.Hosting.PostgreSQL](https://www.nuget.org/packages/Aspire.Hosting.PostgreSQL) | `postgresql`          | `Parameters:postgresql-password` |
| RabbitMQ      | [📦 Aspire.Hosting.RabbitMq](https://www.nuget.org/packages/Aspire.Hosting.RabbitMq)     | `rabbitmq`            | `Parameters:rabbitmq-password`   |
| SQL Server    | [📦 Aspire.Hosting.SqlServer](https://www.nuget.org/packages/Aspire.Hosting.SqlServer)   | `sql`                 | `Parameters:sql-password`        |

By overriding the generated password, you can ensure that the password remains consistent between app launches. 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:

```csharp title="C# — AppHost.cs"
var sqlPassword = builder.AddParameter("sql-password", secret: true);

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

```typescript title="TypeScript — apphost.mts"
const sqlPassword = await builder.addParameter("sql-password", { secret: true });

const sql = await builder.addSqlServer("sql", { password: sqlPassword });
await sql.withDataVolume();
const sqldb = await sql.addDatabase("sqldb");
```

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](/fundamentals/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:

- [Tutorial: Connect an ASP.NET Core app to SQL Server using Aspire and Entity Framework Core](/integrations/databases/efcore/sql-server/sql-server-get-started/)
- [Aspire orchestration overview](/get-started/app-host/)