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:
- The tenant catalog — the control-plane database that lists tenants.
- 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
dotnet run --project src/Host/FSH.Starter.DbMigrator -- [verb] [options]| Verb | What it does |
|---|---|
apply (default) | Apply pending migrations. Add --seed to also run the per-tenant seed. |
seed | Run only the seed step per tenant (no migration). |
seed-demo | Provision demo tenants (acme, globex) with users, catalog, tickets, and chat. Dev-only — refuses to run unless ASPNETCORE_ENVIRONMENT=Development. |
list-pending | Print pending migrations without applying anything. |
| Option | Effect |
|---|---|
--tenant <id> | Restrict to a single tenant id (default: all tenants). |
--catalog-only | Migrate only the tenant catalog; skip the per-tenant pass. |
--seed | After apply, also call SeedTenantAsync per tenant. |
-h, --help | Print 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.
# preview, apply, apply+seed, scope to one tenant, catalog onlydotnet run --project src/Host/FSH.Starter.DbMigrator -- list-pendingdotnet run --project src/Host/FSH.Starter.DbMigrator -- applydotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seeddotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --tenant acmedotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --catalog-onlyLocal 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:
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seedTo populate demo tenants for a local demo or test drive:
dotnet run --project src/Host/FSH.Starter.DbMigrator -- seed-demoProduction
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:
- Build & push the new
fsh-apiandfsh-db-migratorimages (same tag). terraform applyto provision/update infra.- Run the migrator as a one-off ECS task:
aws ecs run-taskwith thefsh-db-migratorimage, commandapply, in the private subnets. Wait for it to exit0. - 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-migrationrecipe), build, thenlist-pendingto confirm andapply. - New module — the migrator has its own module wiring. Registering a module touches
DbMigrator/Program.csas well as the API’sProgram.cs; miss it and the module’s migrations silently never run. See Architecture for the full “four places” checklist.
Next
- Local Orchestration with Aspire — how the migrator is wired into the dev loop.
- Deploy to AWS with Terraform — where the migrator step fits in a cloud rollout.