.xcl files. There is no ubx.yaml for XCL stacks — engine, backend, AI settings, scan settings, and history settings are all declared in the XCL source.
The resolution chain
Every configuration field is resolved through a four-level chain. Lower levels set defaults; higher levels override.meta {} and belongs to no context {} receives only the universal workspace {} values, or ubx’s built-in defaults if no workspace block is present.
What goes where
workspace — workspace-wide defaults
Useworkspace {} for settings that apply to every stack in the compilation unit unless a more specific block overrides them.
workspace {} block with a match {} sub-block applies only to stacks whose effective labels match. A universal workspace {} (no match {}) applies first; a label-scoped workspace {} overlays it.
context — group configuration
Usecontext {} to declare a named configuration group that multiple stacks opt into. A context sits between the workspace and per-stack overrides in the resolution chain.
meta { context = "prod_eu" }. All fields declared in the context override the workspace for that stack.
meta inside stack — per-stack overrides
Usemeta {} for settings specific to a single stack. meta {} is the highest priority in the chain — it overrides everything.
meta {} can both opt the stack into a context {} and declare per-stack field overrides. Fields declared directly in meta {} override the context.
The one exception: API keys
API keys are secrets, not source configuration. The Anthropic API key is never read from.xcl files.
Set it via environment variable:
ai.api_key in ubx.yaml (deprecated path — supported for backward compatibility, but prefer the env var). The env var always takes priority.
AI features are enabled when UBX_AI_API_KEY is set and the workspace ai { enabled = true } is declared (or no workspace block disables it). If the API key is absent, AI features are silently skipped regardless of what the workspace block declares.
Runtime environment overrides
Some settings can be overridden at runtime via environment variables without touching source files. These are useful for CI and per-deployment customisation.| Environment variable | What it overrides |
|---|---|
UBX_AI_API_KEY | Anthropic API key — always takes priority over ai.api_key in ubx.yaml |
PULUMI_BACKEND_URL | State backend URL — overrides the backend {} block in the workspace |
UBX_TELEMETRY_ENDPOINT | Telemetry collection endpoint — overrides the built-in default |
PULUMI_BACKEND_URL is useful when the same source files must deploy to different backends in different environments (e.g., local development vs. CI vs. production). The XCL backend {} block is lower priority than this env var.
UBX_TELEMETRY_ENDPOINT is intended for self-hosted telemetry. The built-in default points to Ubiquex infrastructure; set this variable to redirect telemetry to your own collector.
History storage
Apply history is controlled by thehistory {} sub-block in workspace {}, context {}, or meta {}:
ubx-history.json to the same storage location as the Pulumi state file. For S3, GCS, and Azure Blob backends, history is stored in the same bucket/container alongside the stack state. For the local file backend, history is stored at .ubx/ubx-history.json in the stack directory.
History is enabled by default when no history {} block is declared. Declare history { enabled = false } to opt out.
The history {} block can appear at any level of the resolution chain. A meta {} override takes priority:
Migration guide: from ubx.yaml
If you have an existingubx.yaml, the table below maps each key to its new location in a workspace {} block.
| ubx.yaml key | New location |
|---|---|
backend: s3://bucket/state | workspace { backend s3 { bucket = "bucket" key = "state" } } |
backend: file:///abs/path | workspace { backend local { path = "/abs/path" } } |
backend: https://api.pulumi.com | workspace { backend pulumi { } } |
history.enabled: false | workspace { history { enabled = false } } |
ai.enabled: true | workspace { ai { enabled = true } } |
ai.model: claude-opus-4-8 | workspace { ai { model = "claude-opus-4-8" } } |
ai.features.plan_summary: true | workspace { ai { features { plan_summary = true } } } |
ai.features.cost_estimate: false | workspace { ai { features { cost_estimate = false } } } |
scan.enabled: true | workspace { scan { enabled = true } } |
scan.severity: critical | workspace { scan { severity = "critical" } } |
ai.api_key: "sk-ant-..." | Set UBX_AI_API_KEY env var — do not put secrets in source files |
telemetry.endpoint: "..." | Set UBX_TELEMETRY_ENDPOINT env var |
project:, stack:, and default_tags: remain in ubx.yaml and continue to be read from there. Only the fields listed above have moved to XCL workspace blocks.
A complete workspace block replacing a typical ubx.yaml
.xcl file in your stack directory (e.g. workspace.xcl). ubx discovers all .xcl files in the directory — there is no required filename for workspace {} blocks.

