Skip to content

Configuration

Everything is an environment variable (FABRIC_*); flags override env; then validation derives dependent values. There is no config file.

EnvFlagMeaning
FABRIC_ENTRA_ISSUER-entra-issuerThe exact iss bearer tokens must carry — an entra-emulator issuer (https://entra-emulator:8443/{tenant}/v2.0) or a real Entra tenant (https://login.microsoftonline.com/{tenant}/v2.0). Startup fails without it.
EnvFlagDefaultMeaning
FABRIC_ADDR-addr:9443Listen address.
FABRIC_DATA_DIR-data-dir./dataSQLite + TLS state directory, so workspaces, items and OneLake contents survive a restart. Set it to the empty string for a fully in-memory run (fresh DB and ephemeral cert — ideal for tests): unset and set-empty differ deliberately, and the compose files use the empty form so a throwaway stack leaves nothing behind. The Docker image sets /data.
FABRIC_ENTRA_JWKS_URL-entra-jwks-urlderivedWhere signing keys are fetched. Derived from the issuer by the Entra convention: {origin}/{tenant}/v2.0 → {origin}/{tenant}/discovery/v2.0/keys. Set explicitly only when the JWKS lives elsewhere (e.g. reachable under a different host than the advertised issuer).
FABRIC_ENTRA_TLS_INSECURE-entra-tls-insecurefalseSkip TLS verification when fetching the JWKS — needed when entra-emulator serves its self-signed cert (the compose file sets it). Never needed against real Entra.
FABRIC_AKV_VAULT_HOST—(none)One extra host:port a vault secret reference may resolve against, besides Azure’s own *.vault.azure.net and the sovereign equivalents. The compose file sets keyvault-emulator:8444. This is a security boundary: resolving a reference sends a vault-audience bearer token to the host the connection names, so anything unlisted is refused — otherwise a hostile vaultURI exfiltrates the token, and (pointed at a real tenant) a real Azure one. Azure constrains the URI the same way.
FABRIC_ARM_URL-arm-url(none for the binary; this repo’s compose sets it)arm-emulator origin. Microsoft.Fabric/capacities created over ARM appear on GET /v1/capacities. The binary still defaults to empty, so a bare fabric-emulator is standalone; the compose runs arm-emulator and points at it, because capacities ARE ARM resources and a stack without one is a visibly smaller Fabric. Set FABRIC_ARM_URL= explicitly empty to opt back out to the seeded local capacity — standalone fabric-cicd works either way.
FABRIC_ARM_POLL_SECONDS-arm-poll-seconds5How often the ARM capacities feed is refreshed. 0 uses the 5s default. A transient ARM outage leaves last-known capacities in place.
FABRIC_DISABLE_TLS-disable-tlsfalseServe plain HTTP instead of self-signed TLS. Handy for curl exploration or behind a TLS-terminating proxy.
—-lro-delay0Virtual seconds an async operation stays Running before succeeding. 0 = completes on the next poll. Combine with clock control for deterministic polling tests.
FABRIC_WAREHOUSE_RETENTION_DAYS-warehouse-retention-days30How far back OPTION (FOR TIMESTAMP AS OF …) reaches on a Warehouse, in days (Fabric: 1–120). An older instant is refused by name, and the data files only older versions reach are removed (the Delta log is kept whole). Outside 1–120 the emulator refuses to start rather than clamping. See 35-warehouse-time-travel.md.
FABRIC_WAREHOUSE_VERSIONING-warehouse-versioningonCommit each accepted data-changing statement on a Warehouse to Delta, which is what gives time travel a past to read. off disables it: each such statement otherwise also reads the table and writes a Parquet file, which a build with large tables may not want.
FABRIC_FORCE_LRO—falseMake every API that documents both a synchronous and an asynchronous outcome answer the asynchronous one, so a client’s poll loop is exercised here rather than by a real tenant (49-async-outcome-audit.md). The older, narrower spelling FABRIC_DEFINITION_LRO still works. CI runs the whole fabric-cicd publish this way.
FABRIC_NAME_RESERVATION—0How long a deleted item’s display name stays reserved, as a Go duration (30s). Measured on a real tenant: recreating a Notebook under a just-deleted name answers 409 ItemDisplayNameNotAvailableYet for ~20s. 0 frees the name immediately, which is the permissive direction — set it to make a provision/teardown/re-provision loop face what Fabric does.
FABRIC_LIST_PAGE_SIZE-list-page-size0Page size for the list APIs. 0 = the 100 default; a small value forces every client through the continuation-token loop, which is how you prove one handles pagination before real data grows into it. Negative disables paging.
FABRIC_MAX_REQUEST_BYTES—335544320 (320 MiB)The outer ceiling on any inbound request body, installed once at the root handler. 0 means unlimited, which is what the emulator did before this existed. Measured exposure it closes: 68 json.NewDecoder(r.Body) sites read through no ceiling at all — a streaming decoder allocates as it reads, so any client could make the emulator allocate without limit on any of them. An oversized body with a declared Content-Length is refused 413; a chunked one is capped mid-read. 320 MiB is deliberately above every inner ceiling (MaxBlobWrite is 256 MiB): httpx.ReadBounded detects overflow by probing max+1, so an outer bound set at an inner ceiling would replace that package’s specific fit-vs-truncated message with net/http’s generic refusal. Large Delta/Parquet uploads are unaffected. Set 0 if you already post something larger — a released binary’s accepted inputs must not narrow with no way back. See 62-performance-regressions.md.
FABRIC_TERMINAL_URL—(empty)A ttyd origin (e.g. http://ttyd:7681) that makes the portal’s Flow view offer a terminal pane, proxied through the portal’s own origin. Empty means the feature does not exist — no route is mounted. Opt-in because it is arbitrary execution, not another read, on an otherwise unauthenticated portal. Needs --profile terminal plus docker-compose.terminal.yml.
FABRIC_TERMINAL_TOKEN—generatedThe bearer the terminal proxy requires. Generated at startup when FABRIC_TERMINAL_URL is set, and printed once on stdout for the operator to paste into the pane — no endpoint serves it, because anyone who can reach the portal could then read it. Set it only to pin a value for a scripted demo.

Booleans accept 1, true, yes, on (case-insensitive); anything else is false. Retry-After on 202 responses is fixed at 1 second.

Empty by default for the bare binary — leave them unset and unattached surfaces answer with an honest 501, not a fake result. Point them at running sidecars to route to real compute; see real compute and warehouse TDS for the full setup.

docker compose up sets FABRIC_SPARK_AGENT_URL/FABRIC_SQL_TDS_ADDR/ FABRIC_WAREHOUSE_SQL_URL for you by default (via the auto-loaded docker-compose.override.yml, which also brings up the sidecars they point at) — the binary’s own empty defaults below only apply if you run it directly, or opt out with docker compose -f docker-compose.yml up.

EnvFlagDefaultMeaning
FABRIC_SPARK_LIVY_URL-spark-livy-url(empty)Livy endpoint for real Spark session/statement execution.
FABRIC_SPARK_AGENT_URL-spark-agent-url(empty)Spark agent endpoint fronting the Livy cluster.
FABRIC_SQL_TDS_ADDR-sql-tds-addr(empty)TDS listen address for the SQL analytics endpoint.
FABRIC_WAREHOUSE_SQL_URL-warehouse-sql-url(empty)Backing SQL engine URL for warehouse query execution.
FABRIC_TSQL_STRICT-tsql-strictfalseRefuse T-SQL that the SQL Server backend accepts and real Fabric rejects — recursive CTEs, triggers, enforced constraints and the rest of 29-tsql-parity.md’s Class B. Off by default because it removes capability: it buys “locally green means Fabric-green” at the cost of failing SQL that works today.
FABRIC_TENANT_ADMINS-tenant-admins(empty)Comma-separated principal ids (oid, or appid for a service principal) that count as Fabric administrators for /v1/admin/*. The gate is graded as Microsoft documents it: a read needs a Fabric administrator or a service principal, a write needs a Fabric administrator with no service-principal escape. Empty means nobody is an administrator, so every admin mutation is refused — the honest default, since the alternative is the pre-gate behaviour where every authenticated caller could create governance domains. Membership is declared here, never inferred from a token claim.
FABRIC_WEB_ACTIVITY-web-activityrealstub makes pipeline Web activities record success without calling anything — for a CI leg that must not reach the network. The default performs the real HTTP request, because a fabricated response is a false pass.
FABRIC_CUSTOM_ACTIVITY-custom-activityrealoff makes pipeline Custom (Azure Batch) activities refuse by name so no command reaches the Spark agent. The default runs the command in the agent’s container, matching a notebook cell on that same machine. The old opt-in spelling shell remains on.
FABRIC_AIRFLOW_URL-airflow-url(empty)Apache Airflow 2.10 REST API base URL. Setting it enables real ApacheAirflowJob execution.
FABRIC_AIRFLOW_DAG_DIR-airflow-dag-dir(empty)Host path shared with Airflow’s DAG folder. Required when FABRIC_AIRFLOW_URL is set.
FABRIC_AIRFLOW_USERNAME-airflow-username(empty)Optional Airflow REST basic-auth username.
FABRIC_AIRFLOW_PASSWORD-airflow-password(empty)Optional Airflow REST basic-auth password.
FABRIC_MLFLOW_URL-mlflow-url(empty)Real MLflow tracking/model-registry server base URL. Enables the workspace-scoped MLflow proxy and typed experiment/model synchronization.
FABRIC_KQL_URL-kql-url(empty)A real Kusto engine (Microsoft’s kustainer container, or any ADX cluster) that the Eventhouse / KQL Database surface relays queries, management commands and inline ingestion to. The emulator still terminates Fabric’s own contract — bearer validation, workspace RBAC, per-database isolation — and only the KQL executes upstream. Empty → an honest 501. See 25-rti-kusto.md.
FABRIC_KAFKA_BOOTSTRAP-kafka-bootstrap(empty)Apache Kafka broker host:port behind Eventstream items. Empty → item create still works (mgmt) and Spark / Custom-source execution 501s. See 51-eventstream-kafka.md.
FABRIC_DATABRICKS_URL-databricks-url(empty)databricks-emulator (or real workspace) origin. When set, DatabricksNotebook / DatabricksSparkPython submit there and dbfs: / /Workspace paths become legal. Empty keeps local Spark-agent termination and refuses those paths by name.
FABRIC_DATABRICKS_TOKEN-databricks-token(empty)PAT or OIDC access token sent to FABRIC_DATABRICKS_URL.
FABRIC_DATABRICKS_TLS_INSECURE-databricks-tls-insecurefalseSkip TLS verification for FABRIC_DATABRICKS_URL (databricks-emulator’s self-signed cert).
FABRIC_DAX_URL—(empty)HTTP origin of a DAX pump in front of msmdsrv (POST /v1/deploy then POST /v1/dax). Empty keeps the in-process bounded evaluator — and is what GitHub-hosted ubuntu/mac CI tests. Set-but-unreachable is a 502, not a fallback. Not a compose default, and not a macos-latest / ubuntu-latest sidecar — 52-msmdsrv-hosts.md.
CommandDoes
fabric-emulator versionprints the release version (dev for source builds)
fabric-emulator healthcheckprobes /health on the local instance (honors FABRIC_ADDR), exit 0 when healthy — this is the Docker image’s HEALTHCHECK, since distroless has no shell

fabric-emulator compares the token’s iss claim string-exactly against FABRIC_ENTRA_ISSUER. Tokens carry whatever issuer entra-emulator advertises (its login origin), which is not automatically the hostname you fetch the JWKS from. On the compose network entra is therefore told to advertise the network-internal origin:

entra-emulator:
environment:
ORIGIN_MODE: compat
PUBLIC_ORIGIN: "https://entra-emulator:8443"

so iss = https://entra-emulator:8443/{tenant}/v2.0 = what fabric validates. If tokens are rejected with an issuer mismatch, this alignment is almost always the cause. See TLS & hosts for the full story.