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 DOTNET_ENVIRONMENT=Development (the migrator is a generic-host console app, so it reads DOTNET_ENVIRONMENT, not ASPNETCORE_ENVIRONMENT). Under Aspire this runs automatically — see Local 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: the
fsh-starter-db-migrator resource executes apply --seed on each AppHost launch (so the
root admin admin@root.com is seeded), then a dev-only fsh-starter-demo-seeder resource
runs seed-demo to provision the acme/globex demo tenants and their users. The
API is gated on both (WaitForCompletion), so it never starts against an
unmigrated database and the demo logins work the moment the dashboard loads. 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 (Aspire already does this
for you on launch; run it manually only when you’re not using Aspire — set
DOTNET_ENVIRONMENT=Development so the dev-only verb proceeds):
DOTNET_ENVIRONMENT=Development 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.