main triggers validation; successful main builds proceed through a build-and-test stage, package a versioned ZIP artifact, and deploy to the Linux App Service through a workload-identity service connection. Post-deploy verification confirms the health endpoint reaches the Entra sign-in boundary before the pipeline completes.
Pipeline overview
The pipeline has two stages:Pipeline variables
These variables must be set in the Azure Pipelines pipeline configuration for your environment. They are referenced throughout the YAML as$(variableName).
The service connection (
azureServiceConnection) must use workload identity federation and have deployment access scoped to the FluxFinOps App Service only. It should not carry subscription-owner or broad contributor rights.Build stage
1
Install and build the React frontend
The pipeline uses the locked
package-lock.json for a reproducible install, runs the TypeScript compiler for type checking, and produces the production bundle in frontend/dist/:2
Install Python dependencies and run tests
The pipeline installs the full requirements, runs the test suite, compiles every Python source file, and imports the FastAPI application object to catch name-resolution errors that The final import step catches the class of breakage — a model class or database method referenced but not committed — that let a broken commit reach production.
compileall misses:3
Vendor portable production Python wheels
The artifact must work on the App Service Linux container and on Linux WebJob hosts without a build environment. The pipeline installs The vendor directory is cleared at the start of this step so a package upgrade cannot leave a mixed FastAPI module (routing from one release, utilities from another) that fails at App Service startup.
manylinux_2_28_x86_64-compatible wheels directly into the artifact tree:4
Package and publish the ZIP artifact
The staged tree — application source, The main container and Linux WebJob hosts consume the same artifact, so all Python imports resolve identically regardless of which host runs the code.
frontend/dist/, .python_packages/, and documentation — is zipped without the root folder and published as a versioned pipeline artifact:Deployment
The Deploy stage runs only on successful builds of themain branch. It performs a coordinated deployment that protects the live DuckDB database and verifies operational readiness before the pipeline succeeds.
Quiesce DuckDB writers — before deploying, the pipeline places a deployment-quiesce marker on the SCM VFS and restarts the App Service. It then polls until all governed DuckDB jobs have released their file locks (up to 10 minutes). This prevents a deployment from racing an active write.
Migrate persistent storage — on first deploy, if DuckDB exists at the legacy wwwroot/data/flux.duckdb path, the pipeline copies it to /home/data/flux.duckdb (persistent storage, outside wwwroot) and enables WEBSITE_RUN_FROM_PACKAGE=1 so future deploys mount the ZIP read-only.
ZIP deploy to Linux App Service:
az webapp config appsettings set is additive: it updates only the named keys and preserves all others, including Key Vault references for FLUX_DEEPSEEK_API_KEY, LM_BEARER_TOKEN, and FLUX_WIKI_API_TOKEN that are provisioned out-of-band. Key production settings applied on every deploy include:
Post-deploy smoke test
After deployment, the pipeline verifies that the protected health endpoint redirects unauthenticated requests to the Entra sign-in boundary:302 (redirect to Entra sign-in) or 401 confirms that Easy Auth is protecting the endpoint. Any other status — including 200 without authentication — fails the pipeline.
The pipeline also verifies App Service operational readiness: state=Running, httpsOnly=true, a non-empty managed identity principal, FLUX_SYNC_WORKER_MODE=external, and the continuous sync worker in Running, Initializing, or InactiveInstance state.
Production schedules
Flux uses independent scheduled WebJobs for each data source. All jobs enqueue focusedsync_runs requests rather than launching competing DuckDB writers — the singleton continuous worker serializes all persistence.
The cost-history WebJob backfills 90 days on the first successful collection for a new subscription/cost-type scope, then refreshes only the most recent 14 days on subsequent runs. An automated Cost Details fallback fills checkpointed calendar months when the Query API persistently fails for a scope — at most 4 reports per daily run.
Service connection requirements
TheazureServiceConnection pipeline variable must reference a service connection that:
- Uses workload identity federation — no client secret or certificate.
- Has deployment access scoped to the FluxFinOps App Service only — not subscription-owner or broad contributor rights.
- Can call
az webapp restart,az webapp config appsettings set, andaz webapp showfor the target resource group. - Can reach the SCM/Kudu hostname (
*.scm.azurewebsites.net) for DuckDB quiesce and WebJob status checks.