How to Manage EF Core Migrations in CI/CD Pipelines

Database migrations are easy to manage during development. You update your entity classes, generate a migration, and apply it to your local database. Most of the time, everything works as expected.

The challenge starts when the same application is deployed through a CI/CD pipeline, especially when multiple environments share the same deployment process.

A migration that works locally may fail in production because of missing permissions, existing data, or differences in database schema. If migrations are executed without proper control, a simple application deployment can turn into a database recovery activity.

For applications using Entity Framework Core, the important decision is not just how to generate migrations, but when and how those migrations should be applied.

Why Running Migrations at Application Startup Can Be Risky

EF Core allows applications to apply pending migrations programmatically using Database.MigrateAsync().

This is convenient during development because the application can automatically update its database schema when it starts.

However, I would be careful about using this approach in production.

Consider an API deployed across three application instances. During deployment, all three instances may start around the same time and attempt to apply pending migrations.

EF Core 9 introduced migration locking to protect against concurrent migration execution. Earlier versions do not have this built-in protection. Even with locking, startup migrations still introduce operational concerns, including database permissions and application availability.

For production systems, I prefer separating database migration execution from application startup.

Generate Migration Scripts During CI

One practical approach is to generate SQL migration scripts as part of the continuous integration process.

The code examples in this article are simplified for demonstration purposes and are not taken from an actual production project.

Bash
dotnet ef migrations script \
  --idempotent \
  --project src/MyApp.Infrastructure \
  --startup-project src/MyApp.Api \
  --output artifacts/migrations.sql

The --idempotent option generates a script that checks EF Core’s migration history before applying each migration. This allows the script to work against databases that may have different sets of previously applied migrations.

EF Core tracks applied migrations in the __EFMigrationsHistory table by default.

Generating SQL scripts also gives the team an opportunity to review database changes before deployment. This is useful when a migration includes table alterations, index creation, or data updates that could affect existing records.

An idempotent script is not automatically safe, however. It still needs validation against the target database and its existing data.

Use Migration Bundles for Automated Deployment

Another option is EF Core migration bundles.

A migration bundle packages the migration execution logic into a deployable executable. Instead of installing the EF Core CLI on the deployment server, the pipeline can execute the generated bundle.

For example:

Bash
dotnet ef migrations bundle \
  --project src/MyApp.Infrastructure \
  --startup-project src/MyApp.Api \
  --output artifacts/efbundle

The deployment pipeline can then execute the bundle:

Bash
./artifacts/efbundle \
  --connection "$DATABASE_CONNECTION"

The bundle applies pending migrations using EF Core’s migration history.

This approach is useful when database migrations need to be automated but executed independently of application startup.

The deployment environment must still provide the required runtime dependencies, unless the bundle is published as self-contained. It must also have access to any configuration files required by the application.

Control When Migrations Are Applied

For production deployments, I recommend treating database migration as a separate deployment stage.

A typical pipeline might follow this sequence:

  1. Build and Test: Compile the application and run automated tests
  2. Generate Migration Artifact: Produce a SQL script or migration bundle
  3. Validate and Approve: Review changes and test against a representative database
  4. Apply Database Migration: Execute once using controlled deployment credentials
  5. Deploy Application: Release the compatible application version and verify its health

For staging environments, migration execution may be fully automated. For production, a manual approval step may be appropriate, depending on the risk and operational requirements.

The database migration stage should use credentials with the permissions needed for schema changes. The application itself should ideally use a separate database account with more restricted permissions.

Plan for Rollback and Compatibility

One common mistake is assuming that rolling back the application also rolls back the database.

It doesn’t.

Suppose a migration removes a column that the previous application version still uses. If the new deployment fails and the application is rolled back, the previous version may no longer work.

A safer approach is to introduce schema changes gradually. For example, add a new column first, deploy application code that supports it, migrate existing data, and remove the old column only after it is no longer needed.

This is commonly called the expand and contract approach.

For critical production databases, prepare a tested recovery procedure and backup strategy. EF Core migrations should not be treated as a replacement for database recovery planning.

Final Thoughts

EF Core provides the tools needed to manage database schema changes, but it doesn’t decide how those changes should fit into your deployment process.

For smaller applications, automatic migration execution may be acceptable. For systems with multiple instances, production data, or strict availability requirements, I prefer generating migration artifacts and executing them through a controlled pipeline stage.

The important part is keeping database changes predictable, reviewable, and separate from application startup.

Sources

  • Microsoft Learn: Applying Migrations in EF Core. Covers SQL scripts, migration bundles, deployment strategies, and migration locking. Microsoft Learn
  • Microsoft Learn: Managing Migrations. Explains migration creation, management, and handling existing migrations. Microsoft Learn
  • Microsoft Learn: Migrations in Team Environments. Discusses reviewing migration scripts and controlling database changes in deployment pipelines. Microsoft Learn
  • Microsoft Learn: What’s New in EF Core 9. Documents the introduction of migration locking and related improvements. Microsoft Learn
Assi Arai
Assi Arai
Articles: 62