# Deployment A short overview of how the service runs in production. For provisioning, see [Infrastructure bootstrap](bootstrap.md) and [Set up a new environment](setup-new-env.md). ## Runtime topology The service runs as a single container on Azure App Service, one App Service plan per environment (`dev`, `staging`, `prd`). The container is built from the Dockerfile in the repo root and pushed to the shared Azure Container Registry by CI. ```mermaid flowchart LR user[Caller
EspoCRM, etc.] appsvc[Azure App Service] user -->|HTTPS| appsvc appsvc -->|managed identity| kv[Key Vault] appsvc -->|AAD token or password| pg[(PostgreSQL)] appsvc -->|HTTPS| llm[LLM provider
via LiteLLM] ``` ## Container lifecycle `entrypoint.sh` runs two things, in order: 1. **`python -m qfa.cli.migrate`** — applies any pending Alembic migrations. Uses a Postgres advisory lock (`pg_advisory_lock(LOCK_KEY)`) scoped to the connection, so concurrent replicas wait for one migrator to finish and a crashed migrator's lock auto-releases when its connection closes. 2. **`uvicorn qfa.main:app …`** — binds the HTTP server. Putting migrations before uvicorn means the App Service health probe doesn't see a half-migrated database. The trade-off: container start time grows with migration time, so keep migrations small. ## Database authentication Two modes, selected by `DB_AUTH_MODE`: - **`password`** — `DB_PASSWORD` is read from settings. Simple; suitable for local dev. - **`entra`** — the SQLAlchemy connection acquires an AAD access token via `_AadTokenProvider`, caching it and refreshing 120s before expiry. The App Service system-assigned managed identity must be granted the PostgreSQL role (configured by Terraform). This is the production default. ## Secrets Secrets reach the App Service via [Key Vault references](https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references), not as plain environment variables: | Secret | Used by | |---|---| | `llm-api-key` | `LLMSettings.api_key` | | `llm-api-base` | `LLMSettings.api_base` | | `auth-api-keys` | `AuthSettings.api_keys` (parsed as JSON) | Seeding is described in [Set up a new environment § 6](setup-new-env.md#6-seed-key-vault-secrets). Rotation of `auth-api-keys` is described in [API key management](auth-management.md). ## CI / CD Infrastructure changes: - PR touching `infra/` → CI runs `terraform plan` automatically. - Merge to `main` → trigger `terraform apply` manually from the Actions tab. Application changes: - Merge to `main` → CI builds the container, pushes to ACR, and deploys to the target App Service. Branch protection and deployment gating are configured per environment in the GitHub Actions workflow. ## Recovering from common situations - **Migration is stuck.** Check `pg_locks` for the advisory lock; if it's held by a dead session, the lock will release on connection close (a few seconds at most). If it doesn't, manually kill the holding backend with `pg_terminate_backend(pid)`. - **Managed identity recreated** (after `terraform destroy`/rebuild) — re-run steps 4 and 5 of [Set up a new environment](setup-new-env.md) for the affected environment to refresh `AZ_CLIENT_ID`. - **Usage tracking is broken but app must keep serving requests.** Set `DB_TRACK_USAGE=false` and redeploy. The orchestrator does not depend on the DB for its core operations.