Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Apply EF Core migrations in Aspire
description: Learn about how to to apply Entity Framework Core migrations in Aspire.
description: Learn about how to apply Entity Framework Core migrations in Aspire, including running migrations at startup and publishing migration bundles as container images for deployment.
---

import { Code, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
Expand Down Expand Up @@ -526,3 +526,137 @@ You can find the [completed sample app on GitHub](https://github.com/MicrosoftDo
## More sample code

The [Aspire Shop](https://learn.microsoft.com/samples/microsoft/aspire-samples/aspire-shop/) sample app uses this approach to apply migrations. See the `AspireShop.CatalogDbManager` project for the migration service implementation.

## Automated EF migrations with `AddEFMigrations`

In addition to creating a dedicated worker service as described above, Aspire provides a first-class AppHost API for managing EF Core migrations: `AddEFMigrations`. This API integrates migration execution directly into the AppHost orchestration and the Aspire publishing pipeline, without requiring a separate migration service project.

### Add the package

Add the [📦 Aspire.Hosting.EntityFrameworkCore](https://www.nuget.org/packages/Aspire.Hosting.EntityFrameworkCore) NuGet package to your AppHost project:

```bash title=".NET CLI"
dotnet add package Aspire.Hosting.EntityFrameworkCore
```

### Configure migrations in the AppHost

Call `AddEFMigrations` on any project resource that hosts an EF Core `DbContext`:

```csharp title="C# — AppHost.cs"
var db = builder.AddPostgres("pg").AddDatabase("appdb");

var api = builder.AddProject<Projects.Api>("api")
.WithReference(db);

var apiMigrations = api.AddEFMigrations("api-migrations");
```

The first argument is a resource name for the migration resource. The optional second argument is the fully qualified name of the `DbContext` type; it's only required when the target assembly contains more than one `DbContext`.

If your migrations live in a separate project (for example, `Projects.Data`), reference that project from your AppHost and point to it using `.WithMigrationsProject<Projects.Data>()`:

```csharp title="C# — AppHost.cs"
var apiMigrations = api.AddEFMigrations("api-migrations")
.WithMigrationsProject<Projects.Data>();
```

### Run migrations on startup

To apply migrations automatically when running locally with `aspire run`, chain `RunDatabaseUpdateOnStart()`:

```csharp title="C# — AppHost.cs"
var apiMigrations = api.AddEFMigrations("api-migrations")
.RunDatabaseUpdateOnStart();

// The api project waits for migrations to complete before starting
api.WaitForCompletion(apiMigrations);
```

:::note
`RunDatabaseUpdateOnStart` only affects local run mode. The migration resource is not deployed with the app, so this call has no effect during `aspire publish` or deployment.
:::

When `RunDatabaseUpdateOnStart()` is called, a health check is registered for the migration resource. The resource transitions through the following states:

| State | Description |
|---|---|
| **Pending** | Waiting for database resource to become healthy |
| **Running** | Executing the `dotnet ef database update` command |
| **Finished** | Migrations applied successfully |
| **FailedToStart** | An error occurred during migration |

### Publish migration artifacts

To generate migration artifacts during `aspire publish`, use `PublishAsMigrationScript` or `PublishAsMigrationBundle`:

```csharp title="C# — AppHost.cs"
// Generate both a SQL script and a self-contained bundle during publish
var apiMigrations = api.AddEFMigrations("api-migrations")
.PublishAsMigrationScript()
.PublishAsMigrationBundle();
```

Both methods add pipeline steps that run during `aspire publish` and write their artifacts into the publish output directory under `efmigrations/`.

### Publish the migration bundle as a container image

Pass `publishContainer: true` to `PublishAsMigrationBundle` to wrap the generated bundle in a container image. The migration resource becomes a compute resource that each compute environment (Docker Compose, Azure Container Apps, Kubernetes, and so on) deploys like any other container:

```csharp title="C# — AppHost.cs"
var db = builder.AddPostgres("pg").AddDatabase("appdb");
var api = builder.AddProject<Projects.Api>("api").WithReference(db);

var apiMigrations = api.AddEFMigrations("api-migrations")
.WithReference(db)
.WaitFor(db)
.PublishAsMigrationBundle(publishContainer: true);
```

:::note
Both `.WithReference(db)` and `.WaitFor(db)` are required when using `publishContainer: true`. `.WithReference(db)` wires up the connection string so the migration container can reach the database, and `.WaitFor(db)` ensures the database is healthy before the container starts.
:::

Key behaviors:

- Use the `baseImage` argument to specify a custom base image for the generated container.
- The `targetRuntime` argument defaults to `linux-x64` when `publishContainer: true`; set it explicitly for other architectures (for example, `linux-arm64`).
- Local run mode (`aspire run`) is unaffected — no container image is built, and the migration resource still appears in the dashboard with its tool commands.

#### Preventing container restarts per environment

A migration bundle is idempotent (running it twice is safe), but different compute environments need explicit configuration to avoid restarting the container after it exits.

<Tabs syncKey="deploy-target">
<TabItem label="Azure Container Apps">

Publish the migration resource as an [Azure Container App Job](https://learn.microsoft.com/azure/container-apps/jobs) so it runs once and stops:

```csharp title="C# — AppHost.cs"
var apiMigrations = api.AddEFMigrations("api-migrations")
.WithReference(db)
.WaitFor(db)
.PublishAsMigrationBundle(publishContainer: true)
.PublishAsAzureContainerAppJob(); // requires Aspire.Hosting.Azure.AppContainers
```

</TabItem>
<TabItem label="Docker Compose">

Tell Compose not to restart the container after it exits:

```csharp title="C# — AppHost.cs"
var apiMigrations = api.AddEFMigrations("api-migrations")
.WithReference(db)
.WaitFor(db)
.PublishAsMigrationBundle(publishContainer: true)
.PublishAsDockerComposeService((_, service) => service.Restart = "no"); // requires Aspire.Hosting.Docker
```

</TabItem>
<TabItem label="Kubernetes">

Customize the generated manifest to use a `Job` workload or set `restartPolicy: OnFailure` using your Kubernetes publisher customization hook.

</TabItem>
</Tabs>