ASP.NET Core cookbook · Intermediate

Apply EF Core migrations safely during deployment

Calling Database.Migrate() at startup races between instances and needs schema permissions in the app.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Build a migration bundle in CI and run it as a separate deployment step (or Kubernetes Job) with a migration user, before the new app version starts. Keep migrations backward compatible with the running version.

Solution

# CI: generate an idempotent script for review, and a self-contained bundle to execute
dotnet ef migrations script --idempotent --output artifacts/migrate.sql --project src/Shop.Api
dotnet ef migrations bundle --self-contained -r linux-x64 --output artifacts/efbundle --project src/Shop.Api

# Deploy step (runs once, before rolling out the app)
./artifacts/efbundle --connection "$MIGRATIONS_CONNECTION_STRING"
// Design-time factory so `dotnet ef` can create the context without running the web host
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

public class ShopDbFactory : IDesignTimeDbContextFactory<ShopDb>
{
    public ShopDb CreateDbContext(string[] args) =>
        new(new DbContextOptionsBuilder<ShopDb>()
            .UseSqlite(Environment.GetEnvironmentVariable("MIGRATIONS_CONNECTION_STRING") ?? "Data Source=design.db")
            .Options);
}

public class ShopDb(DbContextOptions<ShopDb> options) : DbContext(options)
{
    public DbSet<Product> Products => Set<Product>();
}

public class Product { public int Id { get; set; } public string Name { get; set; } = ""; }

Pitfalls

  • Migrate() in Program.cs with several replicas starting at once.
  • Destructive migrations (drop/rename column) in the same release as the code change; expand, then contract.
  • The app's runtime database user having DDL permissions.

Variations

  • Review generated SQL in pull requests (migrations script) for locking operations.
  • For PostgreSQL, create indexes concurrently with migrationBuilder.Sql(...) and suppressTransaction: true.

Further reading: Applying migrations

Reference: https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying

More ASP.NET Core recipes

Was this page helpful?