Skip to content
fullstackhero

Guide

Database Migrations (DbMigrator)

The FSH DbMigrator — the one-shot tool that applies EF Core migrations across the tenant catalog and every tenant's per-module databases. The database is never migrated at API startup.

views 0 Last updated

FSH.Starter.DbMigrator is a standalone console app that applies EF Core migrations. It is the single, authoritative way the database schema is created and evolved — locally and in production.

What it does

Each run migrates, in order:

  1. The tenant catalog — the control-plane database that lists tenants.
  2. Every tenant’s per-module databases — one pass per tenant, applying each module’s migrations.

Migrations themselves live in src/Host/FSH.Starter.Migrations.PostgreSQL, organized per module by folder.

Commands

Terminal window
dotnet run --project src/Host/FSH.Starter.DbMigrator -- [verb] [options]
VerbWhat it does
apply (default)Apply pending migrations. Add --seed to also run the per-tenant seed.
seedRun only the seed step per tenant (no migration).
seed-demoProvision demo tenants (acme, globex) with users, catalog, tickets, and chat. Dev-only — refuses to run unless ASPNETCORE_ENVIRONMENT=Development.
list-pendingPrint pending migrations without applying anything.
OptionEffect
--tenant <id>Restrict to a single tenant id (default: all tenants).
--catalog-onlyMigrate only the tenant catalog; skip the per-tenant pass.
--seedAfter apply, also call SeedTenantAsync per tenant.
-h, --helpPrint help.

Exit codes: 0 success · 1 failure (the exception is logged). The non-zero code is what your CI/CD pipeline should gate the deploy on.

Terminal window
# preview, apply, apply+seed, scope to one tenant, catalog only
dotnet run --project src/Host/FSH.Starter.DbMigrator -- list-pending
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seed
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --tenant acme
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --catalog-only

Local development

When you run the stack via Aspire, the migrator runs for you as the fsh-db-migrator resource: it executes apply on each AppHost launch and exits, and the API is gated on its completion (WaitForCompletion) so it never starts against an unmigrated database. See Local Orchestration with Aspire.

You can also run it directly any time:

Terminal window
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seed

To populate demo tenants for a local demo or test drive:

Terminal window
dotnet run --project src/Host/FSH.Starter.DbMigrator -- seed-demo

Production

In production the migrator is the explicit deploy step that runs before the new API serves traffic. It’s published as its own container image (fsh-db-migrator) so you can run it as a one-off task right next to the API.

A typical pipeline order:

  1. Build & push the new fsh-api and fsh-db-migrator images (same tag).
  2. terraform apply to provision/update infra.
  3. Run the migrator as a one-off ECS task: aws ecs run-task with the fsh-db-migrator image, command apply, in the private subnets. Wait for it to exit 0.
  4. Roll out the new API task definition.

Adding a module or a migration

  • New migration — generate it against FSH.Starter.Migrations.PostgreSQL (see the database rules / create-migration recipe), build, then list-pending to confirm and apply.
  • New module — the migrator has its own module wiring. Registering a module touches DbMigrator/Program.cs as well as the API’s Program.cs; miss it and the module’s migrations silently never run. See Architecture for the full “four places” checklist.

Next