Server Installation
Backend
Model A — Azure App Service (recommended, current production model)
- Provision an App Service (Linux, .NET 10) with a System-assigned Managed Identity, plus a Key Vault and Application Insights instance.
- Grant the Managed Identity the Key Vault Secrets User role on the Key Vault — the Graph certificate's private key is stored as a Key Vault secret of the same name as the certificate.
- Deploy the published API (the built SPA is embedded in its
wwwroot) via a test-gated CI/CD pipeline using OIDC federation — no long-lived deployment credentials. - Configure the app settings — see Configuration.
- Confirm
GET /api/healthreturns{"status":"ok"}.
Model B — Self-hosted container
- Build a multi-stage image: build the SPA, publish the .NET API, and combine them into a single image that serves both.
- Mount the Graph certificate (PFX) as a secret; point
Cet2:GraphAuth:CertificateNameat its file path. - Mount a PersistentVolume for the SQLite metadata file (
Cet2:DbPath). Run a single replica — SQLite is single-writer, so this is not horizontally scalable without a persistence-layer change. - Put a reverse proxy or ingress in front to terminate TLS, and add the resulting public hostname as a redirect URI on App 1 (Portal).
- Wire
/api/healthup as the liveness/readiness probe.
:::note Self-hosted packaging status Container artifacts (a reference Dockerfile and Helm chart) for Model B are on the CET2 roadmap and not yet published. Until they land, self-hosting requires building the image yourself from the guidance above. :::
Frontend
The frontend is not deployed separately. It is a Vite-built React SPA that is compiled and copied
into the API's wwwroot at build time — the API serves it directly.
- Node 18+ is required to build it (
npm ci && npm run build). - The client ID and authority baked into the SPA (
authConfig.ts) are build-time values, so the frontend must be rebuilt (not just the backend redeployed) if the Portal application's client ID ever changes. - No separate reverse proxy is needed for the SPA in Model A. In Model B, the same reverse proxy that terminates TLS for the API also serves the SPA, since they're the same process.
Continue to Configuration.