Skip to main content
Flux uses Azure Pipelines for CI/CD with workload-identity federation — no client secrets, publish profiles, or personal access tokens are stored in the repository. Every commit and pull request targeting 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 compileall misses:
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.
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 manylinux_2_28_x86_64-compatible wheels directly into the artifact tree:
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.
4

Package and publish the ZIP artifact

The staged tree — application source, frontend/dist/, .python_packages/, and documentation — is zipped without the root folder and published as a versioned pipeline artifact:
The main container and Linux WebJob hosts consume the same artifact, so all Python imports resolve identically regardless of which host runs the code.

Deployment

The Deploy stage runs only on successful builds of the main 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:
Apply non-secret settings (additive)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:
Resume DuckDB writers — always runs, even if a prior step fails. Removes the quiesce marker so scheduled jobs can resume.

Post-deploy smoke test

After deployment, the pipeline verifies that the protected health endpoint redirects unauthenticated requests to the Entra sign-in boundary:
An HTTP 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 focused sync_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

The azureServiceConnection 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, and az webapp show for the target resource group.
  • Can reach the SCM/Kudu hostname (*.scm.azurewebsites.net) for DuckDB quiesce and WebJob status checks.
No publish profile, client secret, or PAT belongs in the repository or in pipeline YAML.