Releases, Migrations, and Config
Deploying the MVC app is the easy part. The two things that actually break releases are the database and the configuration β so both get engineered, not improvised.
Deploying the app
The published output runs on Kestrel, and the same artifact lands behind whatever front
door the agency operates β IIS as a reverse proxy, Nginx, a container host, or an Azure App
Service (AzureWebApp task). Two habits make deploys boring:
deploy to a staging slot (or secondary site) and swap after a warm-up request succeeds, so
users never hit a cold or half-deployed app; and take the background worker (Hangfire server or
hosted service) offline first, so no analysis job is mid-flight across old and new code.
EF migrations in the release pipeline
Never run migrations from a developer laptop against test or prod, and don't use auto-migration on app start β a farm of two web servers racing to migrate is a corrupted-schema story. Instead, the build stage emits a migration artifact and the release stage applies it before the new app version goes live:
- script: dotnet ef migrations script --idempotent -o $(Build.ArtifactStagingDirectory)/migrate.sql
The idempotent script is the right default: it checks the migrations-history table and applies only what's missing, and DBAs can read and approve the SQL (many agencies require exactly that). Write migrations to be backward compatible one version β add the nullable column now, backfill, then tighten in the next release β so a rollback of the app never needs a rollback of the schema. Practice restores; a migration failing halfway through on prod is the fire drill you want to have rehearsed.
Configuration per environment
The artifact is identical across environments; only configuration differs. Connection strings,
Document Intelligence and OpenAI endpoints/keys, SMTP, file-storage paths β all injected at
deploy time from variable groups (dev/test/prod), with every secret stored in Azure Key
Vault and linked into the group, never in source control or a pipeline YAML. At runtime this
materializes as environment variables layered over appsettings.{Environment}.json, all
read through the same IConfiguration. Rule of thumb: if leaking a value would matter, it lives in
Key Vault; if it merely differs by environment, a variable group is fine.
Feature flags for phased rollout
The Commission won't flip a statewide service on for every EPP at once β and you shouldn't
want them to. Ship dark, then widen: a feature flag system (a FeatureFlags table plus a
cached service, or Azure App Configuration) gates capabilities at runtime, and for this domain
flags want to be per-EPP allowlists, not just booleans. TranscriptSubmission: enabled-EPP list starts with two or three pilot programs; AiPrepopulation can be toggled
independently of submission itself, so if the matching pipeline misbehaves you fall back to
manual review (fail-closed, again) without a deploy. Flags are how "pilot EPPs first" becomes a
config change instead of a release plan.