Skip to content

Deployment — Build & Publish

How a release is built and put on the server, what the build does that is easy to miss, the two publish profiles and where they disagree, and the order a release has to happen in.

Build once per environment

Site URLs, mailboxes and several folder paths are compiled into the assembly (SiteSettings.cs and others — see Configuration). A build made for test carries test values. Build production from a commit whose SiteSettings.cs holds the production values (R4-4).

Build

From the repository root:

dotnet publish NKO.Main/NKO.Main/NKO.Main.csproj -c Release -o publish

The output is framework-dependent net6.0 — the server supplies the runtime through the ASP.NET Core 6.0 Hosting Bundle. dotnet publish builds all five projects through their project references, and also:

Step Detail
Generates the JavaScript bundles BuildBundlerMinifier reads bundleconfig.json and writes four bundles (plus minified copies) to wwwroot/bundles/ — jquery.js, jqueryval.js, modalform.js, scripts.min.js. They are gitignored build outputs, so a fresh clone has none until the first build. There is no npm step; NKO.Main/package.json is empty ({}).
Compiles the Razor views Views are compiled at build time as usual. Runtime compilation is also registered — AddRazorRuntimeCompilation() is called unconditionally in Startup, not only in Development — so in production the app also watches for changed .cshtml files and recompiles them. That costs memory and startup time for a development convenience.
Generates web.config The real site web.config (ASP.NET Core Module, in-process) is created by publish. The Views/web.config in the repository is an unrelated ASP.NET MVC 5 leftover.

What publish does not include:

  • appsettings.json from the repository — it is gitignored. But a local copy on the build machine is packaged, because publish includes every appsettings*.json in the project folder. Publishing from a developer machine that has its own appsettings.json therefore overwrites the server's configuration — connection strings, AWS keys and all. Keep production settings in environment variables (see Configuration), or exclude the file from the package;
  • wwwroot/Rotativa/ — the wkhtmltopdf binaries are not in the repository (R5-3);
  • anything under C:\upload\ — it lives outside the site and is never touched by a publish.

The publish profiles

Deployment is done with Web Deploy from two IIS profiles under NKO.Main/Properties/PublishProfiles/. They are not in the repository (*.pubxml is gitignored), so they exist only on the machines they were created on. Both publish a Release, framework-dependent net6.0 build through the Web Deploy remote agent (MSDeploy, RemoteAgent) and ask for Web Deploy's automatic backup (EnableMSDeployBackup = true). They differ in two settings:

Setting IISProfile IISProfile1 Effect
SkipExtraFilesOnServer false true With false, every file on the server that is not in the package is deleted
EnableMsDeployAppOffline false true With true, Web Deploy drops app_offline.htm first, which stops the app and releases its files

IISProfile removes the PDF engine on every publish — R4-3

wkhtmltopdf.exe is placed in wwwroot\Rotativa\ by hand — it is not in the project, so it is not in the package. With SkipExtraFilesOnServer = false, Web Deploy deletes it, and every PDF export breaks after the publish. The same profile also leaves the site running while it overwrites the application's assemblies; in-process hosting keeps them loaded, so the publish can fail part-way with "file in use" errors, leaving a mix of old and new files.

Use IISProfile1's settings — keep extra files, take the app offline — or add the Rotativa folder to the project so it is part of the package.

Web Deploy's backup only happens if backups are enabled for the site on the server; the profile setting requests it but cannot turn it on. Do not rely on it as the release backup — see Backup & Rollback.

Database migrations

Migrations are not applied at startup — nothing in Program.cs or Startup calls Migrate(). They must be applied as a release step, before the new code starts, for both contexts.

Generate idempotent SQL scripts, which a DBA can review and which are safe to re-run. From NKO.Main/NKO.Main:

dotnet ef migrations script --idempotent --context NKO_DBEntities1 `
    --project ../../NKO.Lib/NKO.DataAccess --startup-project . -o core.sql

dotnet ef migrations script --idempotent --context WarehouseDBContext `
    --project ../../NKO.Lib/NKO.Warehouse.DataAccess --startup-project . -o warehouse.sql

The pinned EF tool is too old

.config/dotnet-tools.json pins dotnet-ef 5.0.5 while EF Core is 6.0.15, so the local tool is unsupported against this model and may fail (R5-2). Use dotnet-ef 6.0.x — see Installation — Database Setup.

Before applying a script, check whether it contains a DROP TABLE or DROP COLUMN. Nine of the existing migrations do, and a migration like that cannot be undone without a database restore — see Backup & Rollback.

Release order

flowchart TD
    A["1. Build for this environment<br/>dotnet publish -c Release"] --> B["2. Back up NKO_DB and NKO_DB_Warehouse together"]
    B --> C["3. Take the site offline<br/>app_offline.htm"]
    C --> D["4. Apply core.sql, then warehouse.sql"]
    D --> E["5. Publish with Web Deploy<br/>keep extra files"]
    E --> F["6. Confirm wwwroot\Rotativa\wkhtmltopdf.exe is still there"]
    F --> G["7. Bring the site online and smoke-test"]

Smoke test

The application logs almost nothing (R8-7), so a failed release shows up only by using it:

  • [ ] Sign in, and open a page behind a permission key (the permission query runs on every request).
  • [ ] Open a notification — its link uses SysUrl.
  • [ ] Upload and download an attachment on any document (S3 and StorageConfigs:Systems).
  • [ ] Upload an attachment on an asset-disposal request — fails until R4-1 is fixed.
  • [ ] Export a PDF from Jobs or Advance (the server fetching its own header and footer).
  • [ ] Export a warehouse Excel report (the C:\upload\… folders).
  • [ ] Next morning: check that open requests were re-routed by the 03:00 UpdateNextUserJob.

If the site does not start at all, turn on stdoutLogEnabled in the generated web.config temporarily — with almost no application logging, the ASP.NET Core Module's stdout log is the only place a startup exception is written.