Skip to content

Configuration

Every setting has a KV_* environment variable and a flag; the flag wins when both are set. Only the Entra issuer is required.

FlagEnvDefaultPurpose
--addrKV_ADDR:8444Listen address.
--data-dirKV_DATA_DIR./dataState directory (SQLite + persisted TLS cert), so secrets, keys and certificates survive a restart. Set it to the empty string to opt back into an in-memory DB and ephemeral TLS keys — unset and set-empty differ deliberately, and the compose files use the empty form so a throwaway stack leaves nothing behind.
--entra-issuerKV_ENTRA_ISSUER(required)The exact iss bearer tokens must carry, e.g. https://localhost:8443/{tenant}/v2.0. An entra-emulator or real Entra v2.0 issuer. A comma-separated list trusts several issuers, each validated against its own JWKS; the 401 challenge advertises the first.
--entra-jwks-urlKV_ENTRA_JWKS_URL(derived)Where signing keys are fetched. Derived from the issuer when unset ({issuer − /v2.0}/discovery/v2.0/keys).
--entra-tls-insecureKV_ENTRA_TLS_INSECUREfalseSkip TLS verification when fetching JWKS — for entra-emulator’s self-signed cert on a compose network.
--default-vaultKV_DEFAULT_VAULTemulatorThe vault served on non-vault hosts (localhost, IPs).
--soft-delete-retention-daysKV_SOFT_DELETE_RETENTION_DAYS90Soft-delete recovery window (7–90). Rejected outside that range.
--arm-urlKV_ARM_URL(unset)arm-emulator’s origin. When set, authorization comes from ARM (role assignments + vault access policies) instead of the /_emulator surface.
--arm-scopeKV_ARM_SCOPEderivedThis vault’s ARM resource id. Derived from the subscription, resource group and default vault name when unset.
--arm-subscriptionKV_ARM_SUBSCRIPTION6082bfda-…-9febUsed to derive the scope.
--arm-resource-groupKV_ARM_RESOURCE_GROUPemulator-rgUsed to derive the scope.
KV_ARM_POLL_SECONDS5How often the ARM authorization feed is refreshed.
--purge-protectionKV_PURGE_PROTECTIONoffRefuse purge (403) and report recoveryLevel: Recoverable, as a purge-protected vault does. Also toggleable at runtime: POST /_emulator/purge-protection {"enabled": true}.
--disable-tlsKV_DISABLE_TLSfalseServe plain HTTP (behind a TLS-terminating proxy, or for curl exploration).

--entra-jwks-url and the challenge authority are both derived from --entra-issuer when unset:

issuer https://localhost:8443/{tenant}/v2.0
jwks https://localhost:8443/{tenant}/discovery/v2.0/keys
authority (advertised in the 401 challenge)
https://localhost:8443/{tenant}

Point --entra-issuer at a real Entra tenant and nothing else changes — the vault validates real tokens.

The distroless image sets KV_DATA_DIR=/data and exposes 8444; mount /data to persist state and the TLS cert across restarts. See Installation for the compose contract.

  • Vaults are created on first write (Host-routed) — there is no vault-CRUD API (Architecture § Non-goals).
  • Runtime knobs used only in tests — the controllable clock, fault injection, and the permission map — are set over HTTP through /_emulator (Testing), not via config.

With --arm-url set, the vault’s own settings come from the Microsoft.KeyVault/vaults resource in ARM, because that is where they live in Azure — not from this process’s flags:

SettingFlag (standalone)ARM property (when --arm-url is set)
Purge protection--purge-protectionproperties.enablePurgeProtection
Soft-delete window--soft-delete-retention-daysproperties.softDeleteRetentionInDays
Authorization model(control surface)properties.enableRbacAuthorization
Who may do what/_emulator/*role assignments + access policies

So az keyvault update --enable-purge-protection true changes the running emulator’s behaviour, exactly as it changes a real vault’s. Two guards:

  • An absent vault resource changes nothing. If ARM has no vault at the configured scope, the flags stay in force — absence is not an instruction.
  • An out-of-range window is ignored rather than applied, the same 7–90 validation the flag gets.

This is the default: docker compose up starts arm-emulator and points the vault at it. The stack also seeds what Azure gives you when you create a vault in the portal — the Microsoft.KeyVault/vaults resource and a Key Vault Secrets Officer assignment for the principal that created it — because ARM’s rule is no assignment means no access, and a default stack whose first request is a 403 would be worse than no default at all.

Terminal window
docker compose up # ARM governs
KV_ARM_URL= docker compose up # opt out; or: make up NOARM=1

The seed is scripts/seed-arm.py, deliberately readable: it is also the documentation for doing the same thing by hand against a real vault.