Deploy on Azure with Terraform
Enprivacy 3.0 runs on Azure as a set of Azure Container Apps inside a private virtual network, backed by Azure Database for PostgreSQL, Azure Blob Storage, Azure Key Vault, and Azure AI Foundry as the LLM backend. The Terraform bundle provisions all of it in seven independently applied modules.
Architecture
Section titled “Architecture”flowchart TD
NET["Internet"] -->|"HTTPS"| CF["<b>Cloudflare</b><br/>client TLS · WAF<br/>Authenticated Origin Pulls"]
CF -->|"HTTPS"| AGW["<b>Application Gateway v2</b><br/>own certificate<br/>edge module, optional"]
AGW -->|"HTTPS, internal"| WEB
subgraph aca ["Container Apps environment — internal only, no public load balancer"]
WEB["<b>Web</b><br/>Thruster + Puma"]
JOB["<b>Job</b><br/>Solid Queue"]
OCR["<b>OCR</b><br/>Docling"]
INF["<b>Inference adapter</b><br/>embed · rerank · detect"]
ONE["<b>One-shot jobs</b><br/>db-bootstrap · db-migrate · db-repair"]
end
WEB -.->|"extract"| OCR
JOB -.->|"extract"| OCR
WEB -.->|"embed · rerank · detect"| INF
JOB -.->|"embed · rerank · detect"| INF
subgraph data ["Private data plane — private endpoints, VNet-integrated"]
PG["<b>PostgreSQL 18</b><br/>pgvector · hstore"]
STORE["<b>Storage</b><br/>documents in blob, keyless"]
AI["<b>Azure AI Foundry</b><br/>system-agent LLM"]
KV["<b>Key Vault</b><br/>every secret, delivered<br/>as Key Vault references"]
LAW["<b>Log Analytics</b><br/>all diagnostics<br/>+ optional SIEM Event Hub"]
end
WEB --> PG
WEB --> STORE
WEB --> AI
JOB --> PG
JOB --> STORE
JOB --> AI
ONE --> PG
Key properties of the reference architecture:
- No public data plane. The Container Apps environment is internal-only; Key Vault, Storage, and the AI Foundry account are reached over private endpoints, and PostgreSQL is VNet-integrated. Public traffic enters only via Cloudflare and the Application Gateway.
- Secrets never leave Key Vault. Every application secret is delivered as a Key Vault reference resolved by a user-assigned managed identity — no raw values in app configuration. The vault’s own secrets are write-only in Terraform, so none of their values reaches Terraform state.
- Offline models. The OCR and inference-adapter images carry the model weights they load, with all offline switches set — nothing in Azure contacts the Hugging Face Hub, and each image digest pins its models.
- Digest-pinned images. All images are pinned by
@sha256:digest; the digest is the provenance handle (see Supply Chain Security).
Module layout and apply order
Section titled “Module layout and apply order”Each concern is a root module with its own remote state, applied in order —
every module reads its upstream modules’ outputs via terraform_remote_state:
foundation → keyvault → storage → database → ai → app → edge| Module | Provisions |
|---|---|
foundation |
Resource group (created, or an existing one adopted), VNet + subnets, NSGs, NAT gateway egress, the internal Container Apps environment, Log Analytics |
keyvault |
Azure Key Vault (RBAC, private endpoint), the app’s user-assigned identity, the generated encryption keys and session secret, and placeholders for your supplied secrets |
storage |
The customer-document account (blob, RA-GRS), plus an optional demo-content account |
database |
PostgreSQL 18 Flexible Server, extension allow-list, database, and the three-role least-privilege model (on by default) |
ai |
Azure AI Foundry account and model deployment for the system agents. Its own module because the lifecycle is durable: it holds a regional quota allocation, its name soft-deletes on destroy, and it must survive app-layer teardowns |
app |
Web, Job, OCR and inference-adapter container apps, and the db-migrate and db-repair jobs |
edge |
Optional Application Gateway v2 public ingress for Cloudflare to target (Cloudflare provides the WAF layer) |
How an environment is configured
Section titled “How an environment is configured”Three rules govern everything below, and they are worth reading before the first command:
The Terraform workspace names the environment. TF_WORKSPACE=prod suffixes
every resource name, selects the configuration file, and keeps state in
per-environment blobs. The default workspace is refused. The name must match
^[a-z][a-z0-9]{0,8}$ — at most nine characters, starting with a lowercase
letter, and lowercase letters and digits only — because it is spliced into
names with tight Azure limits, and because storage account names strip other
characters (so lab-01 and lab01 would collide).
One file holds every non-secret setting for every module:
environments/<workspace>.yaml, with a section per module. Changing an
environment means editing that file and re-applying. There are no per-module
variables.tf to fill in and no terraform.tfvars. Copy environments/example.yaml — it is fully
commented — and keep it complete, because that file is the reviewable record of
how your environment differs from the defaults.
Secret values never pass through Terraform variables. The keyvault
module generates the encryption keys and the session secret, and seeds each
operator secret as REPLACE_ME. Every vault secret is write-only and written
once: Terraform sends the value at creation and never again, so an apply
never reverts a value you set, and none reaches Terraform state. You set the
operator values in Key Vault directly, or through the Azure DevOps pipeline.
Terraform state still holds the database passwords and the storage and AI
account keys, so treat the state account as sensitive.
Prerequisites
Section titled “Prerequisites”- An Azure subscription,
az login, andexport ARM_SUBSCRIPTION_ID=<sub-id>(theazurerm4.x provider requires it explicitly). - Terraform 1.15 (1.11 at minimum: the bundle uses write-only arguments and ephemeral resources).
- A resource group to deploy into. The
foundationmodule creates one, or adopts an existing group named infoundation.resource_group_name— then it never creates or destroys it. Adoption suits a principal scoped to one group. - The apply principal needs, on that resource group, Contributor plus Role
Based Access Control Administrator — or Owner, which is both. The bundle
creates several role assignments and two custom role definitions scoped to the
group; Contributor alone fails late, after resources exist. Creating the
group also needs
resourceGroups/writeon the subscription. It also needs Storage Blob Data Contributor on the state storage account, without whichterraform initfails before anything is created. - Resource providers registered on the subscription, once, by someone with
subscription rights — the bundle never registers them itself:
Microsoft.App,Microsoft.CognitiveServices,Microsoft.DBforPostgreSQL,Microsoft.Insights,Microsoft.KeyVault,Microsoft.ManagedIdentity,Microsoft.Network,Microsoft.OperationalInsights,Microsoft.Storage(plusMicrosoft.Securitywith Defender for Containers). The bundle’s.azuredevops/bin/register-resource-providers.shdoes this idempotently. - A storage account for Terraform state (any resource group; the bundle’s
backend.hcl.exampledocuments the three values needed).bootstrap.shcreates one, and takes its target explicitly — there is no default subscription, deliberately:Terminal window SUBSCRIPTION=<subscription-id> SA=<globally-unique-account-name> ./bootstrap.sh - From Enprivacy: the release image digest
(
ghcr.io/enprivacyhq/enprivacy@sha256:…), the inference adapter image digest, a read-only GHCR pull token and the username it belongs to (see Container Image), your OIDC client id/secret, and your allowed organisation codes. Verify image signatures first — see the Deployment Guide. - A public hostname for the app (e.g.
enprivacy.client.example) and, if you terminate at Cloudflare, the ability to issue a Cloudflare Origin CA certificate for it. - Outbound access from wherever you run Terraform to the Terraform registry
(the AVM modules download at
init) and, from Azure, toghcr.io,quay.ioanddocker.io.
The Azure Pre-Install Checklist turns all of this into a form to work through before the deployment session.
Step 1 — configure state and pick a workspace
Section titled “Step 1 — configure state and pick a workspace”cd terraform/azurecp backend.hcl.example backend.hcl # fill in your state storage accountaz loginexport ARM_SUBSCRIPTION_ID=<subscription-id>export TF_WORKSPACE=<env> # e.g. prod — see the naming rule abovecp environments/example.yaml environments/$TF_WORKSPACE.yamlEvery module is initialised against the same backend with a per-module key:
cd foundationterraform init -backend-config=../backend.hcl -backend-config="key=azure/foundation.tfstate"terraform workspace select -or-create "$TF_WORKSPACE"The bundle ships mise tasks that do this for every module in dependency
order — mise run init:all, then mise run plan|apply <module>…. The examples
below name the module so either route is clear.
Step 2 — foundation
Section titled “Step 2 — foundation”mise run apply foundationReview the foundation: section of your environment yaml first: location
(default southeastasia), vnet_address_space if 10.0.0.0/16 collides
with your address plan, and resource_group_name to adopt an existing group
(set location to that group’s region).
The app-subnet NSG implements a reduced-egress posture: outbound is denied except DNS, NTP, intra-VNet, and 443 (required for GHCR pulls, the OIDC back-channel, and mandatory Container Apps platform traffic).
Step 3 — keyvault
Section titled “Step 3 — keyvault”Set two things in the keyvault: section of your environment yaml before
applying, both load-bearing when you apply from outside the VNet:
| Setting | Purpose |
|---|---|
deployer_object_id |
Object id of the principal running apply (az ad signed-in-user show --query id -o tsv). Grants it Key Vault Secrets Officer and Certificates Officer — the latter is what lets you import the App Gateway certificate in Step 7. |
deployer_ip_allowlist |
Your public IP (bare, no /32), allowed onto the otherwise private-endpoint-only vault. Without it, every secret write from this module onwards returns 403. Leave empty in production, where applies run from inside the VNet. |
purge_protection |
true by default — keep it for production. false makes test environments fully destroyable (otherwise the vault name stays reserved for 90 days after destroy). Cannot be disabled on an existing vault. |
mise run apply keyvaultThe module generates the three Active Record encryption keys
(enc-primary-key, enc-index-key, enc-key-derivation-salt) and the Rails
session secret (secret-key-base) inside Key Vault. Never set or rotate these
by hand. It seeds the operator secrets as REPLACE_ME; set their real
values now — directly in the vault, or with the Azure DevOps pipeline’s
Secrets stage:
VAULT=enprivacy-kv-$TF_WORKSPACEaz keyvault secret set --vault-name "$VAULT" --name auth-client-id \ --value "<client id from Enprivacy>"az keyvault secret set --vault-name "$VAULT" --name auth-client-secret \ --value "<client secret from Enprivacy>"az keyvault secret set --vault-name "$VAULT" --name ghcr-token \ --value "<GHCR pull token from Enprivacy>"Terraform writes each value once and never again, so later applies never revert
them. ghcr-token gates every image pull in the stack — if it is wrong,
expired or still REPLACE_ME, containers fail to start with a pull error and
nothing downstream works. Replace it before it expires: set a new version, then
restart each app’s revision, since references resolve when a revision starts.
The inference adapter has two more operator secrets, its API key pair, set before its first start. Inference Adapter explains minting them; on Azure they go into the vault as:
| Secret | Holds |
|---|---|
inference-api-key |
The key half from keygen — the platform’s |
inference-api-keys |
The entry half — the adapter’s |
The adapter’s TLS certificate is not an operator secret. The app module
has Key Vault generate it, self-signed and valid for 24 months, for the names
the platform dials. Its private key is created in the vault and never leaves
it. Web and Job mount the certificate alone; only the adapter mounts the key.
To rotate it before it expires (inference_certificate output), raise
app.inference_tls_generation by one, apply app, and restart the adapter,
Web and Job. The bundle’s RUNBOOK.md (“The inference adapter’s key pair and
certificate”) has the details.
Step 4 — storage, Step 5 — database
Section titled “Step 4 — storage, Step 5 — database”mise run apply storage databaseThe database module provisions PostgreSQL 18 with the extensions the schema
needs — hstore, vector and plpgsql — on the azure.extensions allow-list
(see the Deployment Guide).
Least-privilege by default: the module creates a three-role model —
pgadmin (bootstrap only), enprivacy_migrator (schema changes), and
enprivacy_app (runtime, DML-only) — plus a one-shot db-bootstrap job to
create the roles. Run it after the apply:
az containerapp job start -n enprivacy-db-bootstrap-<env> -g <resource-group>From Azure DevOps, the pipeline’s Operations · Run DB bootstrap does the same and waits for the result. Terraform never starts this job, so it has no execution history until one of these runs.
Setting enable_dml_role = false opts into a simplified install instead: the
app connects with the admin credentials, migrates itself at boot, and no manual
database job steps are needed.
Step 6 — ai
Section titled “Step 6 — ai”mise run apply aiThis provisions the Azure AI Foundry account and one model deployment for the
system agents. Set in the ai: section:
| Setting | Value |
|---|---|
llm_model_name / llm_model_version |
The model and version. Version availability is region-limited — confirm the pair exists in your region as GlobalStandard before applying. |
llm_model_capacity |
Thousands of tokens per minute. This is a rate limit, not a reservation: GlobalStandard bills per token, so a high ceiling costs nothing until it is used. The default of 10 is roughly one document-grounded chat turn per minute before requests start returning 429 — raise it for anything beyond a smoke test. |
It is a separate module because its lifecycle is durable. The account holds a regional quota allocation, and its name soft-deletes on destroy — a destroyed account must be purged before the name can be reused:
az cognitiveservices account purge -l <location> -g <rg> -n enprivacy-ai-<env>Step 7 — app
Section titled “Step 7 — app”Set at minimum, in the app: section of your environment yaml:
| Setting | Value |
|---|---|
enprivacy_image |
The release image digest from Enprivacy |
inference_image |
The inference adapter image digest from Enprivacy |
ghcr_username |
The username that owns the GHCR pull token, from Enprivacy |
public_hostname |
The public hostname users will reach the app on — feeds EP_HOST, which drives link generation and the OAuth callback |
allowed_organization_codes |
Sign-in allowlist (provided by Enprivacy) |
admin_team_source / admin_team_emails |
How the Auth Admins team is granted (emails, auth or entra) and, for emails, the comma-separated bootstrap administrators |
mise run apply appRun migrations now — and after every release (not needed only on the
simplified enable_dml_role = false path, where the app migrates itself):
az containerapp job start -n enprivacy-db-migrate-<env> -g <resource-group>Then the post-deploy repairs (bin/rails doctor:repair), once the migration
has succeeded and before web and job start. Each repair is idempotent and
prints a count:
az containerapp job start -n enprivacy-db-repair-<env> -g <resource-group>From Azure DevOps, tick Run DB migrate on Terraform · Plan & apply: it
stops web and job, applies app if ticked, runs the migration and then the
repairs, waiting for each, then starts them again. Terraform never starts
either job, so neither has execution history until one of these runs.
The Web, Job, OCR and inference services deploy at 0 replicas by default
(web_replicas / job_replicas / ocr_replicas / inference_replicas), so
nothing boots against an empty schema. Once migration has succeeded and the
inference adapter’s secrets are set, raise inference_replicas first and
re-apply; once the adapter reports ready, raise the other three. The web and
job apps do not start without a reachable adapter:
mise run apply appThe web and job apps resolve their configuration automatically — database DSN,
OCR and inference endpoints, encryption keys — from module outputs and Key Vault references.
Blob storage is keyless: the apps authenticate to the document account with
their managed identities, so no storage key is ever delivered to them. The LLM
is wired end-to-end too: the stack deploys an Azure AI Foundry model
dedicated to this workload — the system agents send PII-bearing content to
it, so nothing else should share the account — and points the app at its
OpenAI-compatible endpoint (EP_LLM_PROVIDER=azure) with the account key
delivered as a Key Vault reference (EP_LLM_API_KEY, isolated from any
AZURE_OPENAI_* settings used for other models). Keyless (managed-identity)
LLM auth is a planned follow-up; the required role assignments are already
provisioned. The full set of application
environment variables is documented in the
Deployment Guide.
Step 8 — edge (public ingress)
Section titled “Step 8 — edge (public ingress)”The edge module provisions an Application Gateway v2 (Standard_v2 — no WAF on
the gateway, since Cloudflare provides the WAF layer in this design; set the
appgw_sku_* variables to WAF_v2 for an OWASP policy as defence-in-depth) in
front of the internal environment. It is gated off by default; to enable:
- Issue a Cloudflare Origin CA certificate for your public hostname, bundle it as a PFX, and import it into the Key Vault; grant the gateway’s user-assigned identity access to it.
- In the
edge:section, setenable_app_gateway: true,appgw_frontend_hostname(your public hostname), a uniqueappgw_public_ip_domain_label, andappgw_certificate_key_vault_secret_id— the certificate’s unversioned secret id, so a re-import rotates the certificate without a Terraform change. mise run apply edge. Gateway creation takes 6–15 minutes; “Still creating…” at the ten-minute mark is normal — do not interrupt it. Then point a proxied Cloudflare DNS record at the gateway’s public IP and set Cloudflare SSL/TLS mode to Full (strict).- Enforce Cloudflare-only ingress — strongly recommended. Paste
Cloudflare’s public origin-pull CA certificate (PEM) into
edge.cloudflare_origin_pull_cato enable Authenticated Origin Pulls (TF_VAR_appgw_cloudflare_origin_pull_caoverrides it for a one-off): the listener then requires and validates Cloudflare’s client certificate (mTLS), so traffic sent straight to the gateway’s public IP is rejected. Since Cloudflare provides the WAF layer in this design, this is what prevents attackers from bypassing the WAF entirely; also enable Authenticated Origin Pulls on the Cloudflare zone.
Deploying from Windows (PowerShell)
Section titled “Deploying from Windows (PowerShell)”The commands above assume a POSIX shell. Nothing about the stack requires one:
terraform and az both run natively on Windows, and the bundle’s mise tasks
are only convenience wrappers around them. This section is the PowerShell
equivalent, including the handful of translations that are not mechanical.
Prefer PowerShell 7 (pwsh) over Windows PowerShell 5.1 where you have the
choice — curl is an alias for Invoke-WebRequest in 5.1, and the PEM-to-PFX
conversion below needs .NET 5+.
Getting the bundle onto the machine
Section titled “Getting the bundle onto the machine”Take an archive, not a git clone. Beyond keeping repository history off a
client machine, a clone on Windows with core.autocrlf=true — the Git for
Windows default — rewrites the .tf files with CRLF, and Terraform does not
strip CR from a heredoc. The one-shot job scripts would reach their containers
as set -e\r, failing at run time rather than at plan time.
# Extract to a SHORT path: `terraform init` builds deep .terraform\modules\…# trees for the Azure Verified Modules that can exceed the 260-character# MAX_PATH limit under a user profile directory.Expand-Archive -Path $HOME\Downloads\enprivacy-iac.zip -DestinationPath C:\epGet-ChildItem -Recurse C:\ep | Unblock-File # clears the mark-of-the-web flagcd C:\ep\terraform\azureKeep backend.hcl and environments\<env>.yaml outside the extracted tree, so
re-extracting a newer bundle does not take them with it.
Toolchain
Section titled “Toolchain”winget install --id Hashicorp.Terraform # must be 1.15.xwinget install --id Microsoft.AzureCLIaz extension add --name containerappTerraform needs no administrator rights — it is a single binary, so if
winget is blocked you can extract terraform.exe into a user directory and
add it to PATH. The Azure CLI is the opposite: its MSI requires an
administrator. Where that approval is slow, python -m pip install --user azure-cli is a user-scope install, Azure Cloud Shell needs no install at all,
and a service principal removes the CLI from Terraform’s path entirely.
Session setup
Section titled “Session setup”az login$env:ARM_SUBSCRIPTION_ID = "<subscription-id>"az account set --subscription $env:ARM_SUBSCRIPTION_ID$env:TF_WORKSPACE = "<env>"With a service principal instead of an interactive login, and no az login
step:
$env:ARM_TENANT_ID = "<tenant-id>"$env:ARM_CLIENT_ID = "<application (client) id>"$env:ARM_CLIENT_SECRET = "<client secret>"$env:ARM_USE_CLI = "false"init, plan and apply
Section titled “init, plan and apply”Paste these once per session. The $LASTEXITCODE check is the load-bearing
part — PowerShell does not stop when a native command fails, so without it a
failed apply carries straight on into the next module.
$roots = @("foundation","keyvault","storage","database","ai","app","edge")
function Init-Roots { param([string[]]$Modules = $roots) foreach ($m in $Modules) { Write-Host "==> terraform init $m" -ForegroundColor Cyan Push-Location $m terraform init -backend-config=../backend.hcl -backend-config="key=azure/$m.tfstate" $code = $LASTEXITCODE; Pop-Location if ($code -ne 0) { throw "init failed in $m" } }}
function Invoke-Roots { param([Parameter(Mandatory)][ValidateSet("plan","apply","destroy")][string]$Action, [Parameter(Mandatory)][string[]]$Modules) $extra = if ($Action -eq "plan") { @() } else { @("-auto-approve") } foreach ($m in $Modules) { Write-Host "==> terraform $Action $m [workspace $env:TF_WORKSPACE]" -ForegroundColor Cyan Push-Location $m terraform $Action @extra $code = $LASTEXITCODE; Pop-Location if ($code -ne 0) { throw "$Action failed in $m" } }}The deployment sequence then follows the steps above, module by module:
Init-RootsInvoke-Roots -Action apply -Modules foundationInvoke-Roots -Action apply -Modules keyvault# … set the operator secrets, then …Invoke-Roots -Action apply -Modules storage,database,ai# … run the bootstrap job, then app, db-migrate, the replica raise, and edge …Invoke-Roots -Action plan -Modules $roots # the "no changes" check at the endThe operator secrets
Section titled “The operator secrets”The \ line continuations in the examples earlier break when pasted into
PowerShell, whose continuation character is a backtick. Single-line forms:
$vault = "enprivacy-kv-$env:TF_WORKSPACE"
az keyvault secret set --vault-name $vault --name auth-client-id --value "<client id>"az keyvault secret set --vault-name $vault --name auth-client-secret --value "<client secret>"az keyvault secret set --vault-name $vault --name ghcr-token --value "<GHCR pull token>"Your public IP, for the Key Vault allowlist
Section titled “Your public IP, for the Key Vault allowlist”keyvault.deployer_ip_allowlist accepts IPv4 only, and a lookup from a
dual-stack machine can answer with IPv6 — which Key Vault will not accept and
which silently will not match. Force v4:
$ip = Invoke-RestMethod -Uri "https://api4.ipify.org"az keyvault network-rule add --name "enprivacy-kv-$env:TF_WORKSPACE" --ip-address $ipCertificates, without openssl
Section titled “Certificates, without openssl”PowerShell 7 can bundle the Cloudflare Origin CA certificate as a PFX natively:
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile("origin-cert.pem","origin-key.pem")[IO.File]::WriteAllBytes("origin.pfx", $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12))az keyvault certificate import --vault-name "enprivacy-kv-$env:TF_WORKSPACE" -n frontend-cert -f origin.pfxCreateFromPemFile needs .NET 5+; on PowerShell 5.1 use the openssl that ships
with Git for Windows (C:\Program Files\Git\usr\bin\openssl.exe).
Long-running steps and disconnects
Section titled “Long-running steps and disconnects”The Application Gateway takes 6–15 minutes, longer than some idle timeouts. There is no tmux here and none is needed:
disconnecting from a remote desktop session leaves it running, so an apply
survives a dropped connection. What kills it is a sign-out — confirm the idle
policy disconnects rather than logs off. If it does force a logoff, start the
long step as a scheduled task so it is not a child of your session.
Verify the deployment
Section titled “Verify the deployment”- Health:
https://<public-hostname>/upreturns200. - Inference adapter: its replica reports ready, and its boot log names a
CPU budget read from the cgroup that matches
inference_cpu. - Sign-in: complete an OIDC login with a user in an allowed organisation.
- Background jobs: the job app’s log stream shows Solid Queue polling; recurring jobs appear on schedule.
- Document round-trip: upload a document to a workspace and confirm it
lands in the
active-storagecontainer of the document storage account. - No drift:
mise run plan:allreports no changes in every module.
Upgrading
Section titled “Upgrading”Per release:
- Verify the new image signature, then set
enprivacy_imagein your environment yaml to the new digest. mise run apply app.- Three-role model only: run the
db-migratejob immediately after, then thedb-repairjob. On the simplified path, rundb-repaironce the rollout has finished and each app shows one active revision.
A new inference adapter release is the same: set inference_image to its
digest and apply app. Its models come with the image, so nothing is staged.
Feature gates
Section titled “Feature gates”Each lives in the named module’s section of environments/<workspace>.yaml.
| Setting | Module | Default | Effect |
|---|---|---|---|
enable_dml_role |
database | on | Three-role least-privilege database model + bootstrap/migrate jobs; false = simplified admin-DSN install |
enable_app_gateway |
edge | off | Application Gateway v2 public ingress |
enable_defender_for_containers |
app | off | Microsoft Defender for Containers (subscription-scoped, billable) |
siem_eventhub_authorization_rule_id |
common |
off | Stream diagnostics to your SIEM Event Hub in addition to Log Analytics |
enable_apps_workload_profile |
foundation | on | Declare an E8 profile for the runtime apps, unused until web_workload_profile / job_workload_profile name it. Free at zero nodes, and the escape hatch from Consumption’s 8 GiB per-replica ceiling. Changeable in place |
enable_demo_content |
storage | off | A third storage account for operator-uploaded demo documents — the only account in this stack reachable from outside the VNet |
resource_group_name |
foundation | empty | Empty creates <prefix>-<workspace>; a name adopts that existing group, which the bundle then never creates or destroys |
inference_hf_analyser |
app | off | Offer the transformer NER analyser on the inference adapter. Costs ~0.4 GB resident; re-check inference_memory |
enable_vnet_flow_logs |
foundation | off | VNet flow logs + traffic analytics — records all network flows (including internet egress) into storage + Log Analytics; the detective complement to the NSG egress rules |
appgw_sku_name / appgw_sku_tier |
edge | Standard_v2 |
WAF_v2 adds an OWASP WAF policy on the gateway (defence-in-depth behind Cloudflare) |