Skip to main content

Summary

Add global variable sets — named, reusable collections of key-value pairs that can be scoped to a workspace, system, or environment and are automatically injected into deployment variable resolution. Variable sets eliminate the need to duplicate shared configuration across deployments and provide a single place to manage cross-cutting variables like database endpoints, feature flags, region metadata, and shared credentials references.

Motivation

Shared configuration is duplicated across deployments

A typical workspace has configuration that spans many deployments: database connection strings, message queue endpoints, feature flags, regional settings, cloud account IDs, and API keys. Today, each deployment must define its own deployment variable for each of these values. Consider a workspace with 15 deployments that all need DATABASE_URL, REDIS_URL, LOG_LEVEL, and REGION. The operator must:
  1. Create 4 deployment variables on each of the 15 deployments (60 variables).
  2. For each variable, create deployment variable values with the correct resource selectors to differentiate production from staging.
  3. When the staging database endpoint rotates, update the value across all 15 deployments individually.
This is tedious, error-prone, and scales poorly. A forgotten update leaves one deployment pointing at a stale endpoint. There is no mechanism to express “these variables are the same across all deployments in this system.”

No hierarchy for variable inheritance

The current variable model is flat. Deployment variables live on deployments. Resource variables live on resources. There is no intermediate layer where an operator can say “all deployments in this system inherit these variables” or “all deployments in this workspace get these defaults unless overridden.” Other deployment platforms solve this with variable groups (Azure DevOps), variable sets (Terraform Cloud), environment variables (Vercel), or config maps (Kubernetes). Ctrlplane’s closest analog is resource variables, but those are scoped to the resource — they express “this resource has this property,” not “this configuration should flow to all deployments targeting this resource.”

Variable changes need consistent rollout

When a shared value changes, the operator wants all affected deployments to pick up the change atomically. Today, updating 15 deployment variables is 15 separate mutations, each triggering its own release reconciliation. There is no way to batch the update and have all affected release targets reconcile with the new values simultaneously.

The deployment variable model is the wrong abstraction for shared config

Deployment variables answer the question “what configuration does this deployment need?” They are owned by the deployment and vary per deployment. Shared configuration answers a different question: “what configuration exists in this environment/system/workspace that multiple deployments consume?” Overloading deployment variables for shared config creates problems:
  • Ownership ambiguity. Who owns the DATABASE_URL deployment variable — the deployment owner or the platform team that manages the database? When it lives on every deployment, there is no single owner.
  • Drift. Without a single source of truth, values drift between deployments. Deployment A gets updated, deployment B does not.
  • Onboarding cost. Adding a new deployment requires copying all shared variables from an existing deployment. There is no template or inheritance.
  • Audit difficulty. Answering “which deployments use this database endpoint?” requires scanning every deployment’s variables.

Proposal

Variable sets as a first-class entity

A variable set is a named collection of key-value pairs with a scope (workspace, system, or environment) and an optional selector that further narrows which release targets receive the variables. Variable sets are resolved during the variable evaluation phase alongside deployment variables and resource variables. Variable sets are not deployment variables. They are a separate entity with their own lifecycle, ownership, and API surface. They are injected into the variable resolution pipeline as an additional source of values, sitting between deployment variable defaults and deployment variable values in the resolution priority chain.

Schema

Each variable set contains variables:
The value column uses the same JSONB format as existing deployment variable values, supporting both literal values and reference values:

Resolution priority

The variable resolution priority is extended to include variable sets. The full priority chain, from highest to lowest:
  1. Resource variable — a variable defined directly on the resource with a matching key. This is the most specific override and always wins.
  2. Deployment variable value — a deployment variable value whose resource selector matches the target resource, sorted by priority (highest first).
  3. Variable set (environment scope) — a variable set scoped to the specific environment in the release target, sorted by set priority.
  4. Variable set (system scope) — a variable set scoped to the system containing the deployment, sorted by set priority.
  5. Variable set (workspace scope) — a variable set scoped to the workspace, sorted by set priority.
  6. Deployment variable default — the default value defined on the deployment variable.
This ordering follows the principle of specificity: the most specific source wins. Resource variables are the most specific (set on the exact resource), variable sets widen from environment to system to workspace, and deployment variable defaults are the fallback. Within the same scope level, multiple variable sets may define the same key. The set with the highest priority value wins. If two sets at the same scope have the same priority, the one created most recently wins (deterministic tiebreaker). Only keys declared as deployment variables are resolved. Variable sets do not introduce new keys into the release — they provide values for keys that the deployment has declared via deployment variables. A variable set with DATABASE_URL = "postgres://..." has no effect on a deployment that does not declare a DATABASE_URL deployment variable. This preserves the deployment’s contract: the deployment declares what variables it needs, and variable sets (along with other sources) provide the values.

Variable manager changes

The Manager.Evaluate method in the workspace-engine is extended to query variable sets after deployment variable values and before deployment variable defaults:
The tryResolveFromVariableSets method iterates through variable sets in scope order. Sets are pre-sorted: environment-scoped first, then system-scoped, then workspace-scoped. Within each scope level, sets are sorted by priority (descending), then by created_at (descending):

Reconciliation on variable set changes

When a variable set is created, updated, or deleted, the system must re-evaluate all release targets that could be affected. The scope determines the blast radius:
  • Workspace scope: all release targets in the workspace.
  • System scope: all release targets for deployments in the system.
  • Environment scope: all release targets in the environment.
If the variable set has a selector, only release targets matching the selector are re-evaluated. This is the same reconciliation pattern used when deployment variables change — the workspace-engine detects that variable inputs have changed, computes the new resolved variables, and creates a new release if the resolved values differ from the current release.

API

REST

Create:
Upsert variables:
This is a partial upsert — keys included in the request are created or updated, keys not included are left unchanged. To remove a key, use the DELETE endpoint. List with scope filtering:
Returns all variable sets that apply to the given scope, including workspace-scoped sets that apply everywhere.

tRPC

Terraform provider

UI

Variable sets management page

A new page at /workspaces/{id}/settings/variable-sets lists all variable sets in the workspace: Clicking a variable set opens an editor showing:
  • Name, description, scope, priority.
  • A table of variables with key, value (masked if sensitive), and actions.
  • An “Add variable” form.
  • A “Used by” section showing which deployments declare variables with matching keys and would receive values from this set.

Variable resolution preview

The deployment detail page gains a “Variable Resolution” panel that shows, for each deployment variable, where its value comes from for a selected resource: This makes the resolution chain visible and debuggable. Each source is a link to the entity that provided the value.

System and environment detail pages

The system detail page and environment detail page show variable sets scoped to them, with a quick-add button to create a new set at that scope.

Examples

Shared database configuration

A platform team manages database endpoints. They create variable sets per environment:
Every deployment that declares a DATABASE_URL deployment variable automatically receives the correct value based on which environment the release target is in. When the production database endpoint changes, the operator updates one variable set and all deployments pick up the change.

Workspace-wide defaults

A workspace admin sets sensible defaults that apply everywhere:
Individual deployments can override these by setting deployment variable values or deployment variable defaults, which have higher priority. A deployment that needs LOG_LEVEL=debug sets its own deployment variable value — the workspace-wide default is ignored for that deployment.

System-specific configuration

A payment system has configuration shared across its deployments (payment-api, payment-worker, payment-webhook):
All three deployments in the payment system receive these variables without any per-deployment configuration.

Layered overrides

Multiple variable sets at different scopes can coexist. The resolution chain handles precedence naturally:
For a release target in the matching environment, the environment-scoped set wins (it is more specific). For a release target in a different environment within the same system, the system-scoped set provides LOG_LEVEL = "info". For a release target in a different system entirely, the workspace-scoped set provides LOG_LEVEL = "warn". If a resource has a resource variable LOG_LEVEL = "trace", that overrides everything — resource variables are always the highest priority.

Variable set with selector

A variable set can be further narrowed with a CEL selector:
Only release targets whose resource has gpu_enabled: true in metadata receive these variables. Other release targets are unaffected.

Migration

  • The variable_set and variable_set_variable tables are new. No data migration required.
  • Existing deployment variables, resource variables, and their resolution logic are unchanged. The variable set layer is additive.
  • The workspace-engine’s variable manager gains a new resolution step between deployment variable values and deployment variable defaults. Existing resolution behavior is preserved — variable sets only provide values when higher-priority sources (resource variables, deployment variable values) do not.
  • No changes to existing API endpoints. New endpoints are additive.

Open Questions

  1. Variable set assignment vs. scope-based matching. The current proposal uses scope-based matching: a variable set with scope = environment and scope_entity_id = <prod> automatically applies to all release targets in production. An alternative is explicit assignment: the operator attaches variable sets to deployments, systems, or environments manually. Assignment gives more control but requires more configuration. Scope-based matching is simpler but less flexible. Should we support both?
  2. Key collision across sets. When two variable sets at the same scope level define the same key, priority determines the winner. Should collisions be surfaced as warnings in the UI? Should there be a strict mode that rejects ambiguous resolutions?
  3. Sensitive variable handling. The sensitive flag masks values in the UI and API list responses. Should sensitive variables also be excluded from audit logs? Should they require a separate permission to read the plaintext value? How does this interact with the secret provider integration from RFC 0006?
  4. Variable set versioning. Should variable sets support versioning or change history? When a variable is updated, should the system record the previous value for audit purposes? This adds complexity but is valuable for debugging “what changed.”
  5. Bulk update atomicity. When updating multiple variables in a set, should the operation be atomic (all-or-nothing)? The current proposal uses upsert semantics where each key is updated independently. An atomic bulk update would prevent partial updates but requires transactional semantics.
  6. Cross-workspace variable sets. Should variable sets be shareable across workspaces? This is relevant for organizations with multiple workspaces that share infrastructure. The current proposal scopes sets to a single workspace.
  7. Interaction with variable set selectors and deployment variable selectors. A variable set with a selector and a deployment variable value with a resource selector are conceptually similar. Should we unify the filtering mechanism or keep them separate?

Future Considerations

Variable set templates

Pre-built variable set templates for common patterns:
  • Cloud provider defaults: AWS region, account ID, VPC settings.
  • Observability stack: OTEL endpoints, log levels, metrics configuration.
  • Database connection: URL, pool size, SSL mode, timeout.
Templates could be shared across workspaces or published as community templates.

Environment cloning

When creating a new environment (e.g., a new staging environment for a feature branch), variable sets scoped to a reference environment could be cloned automatically with modified values. This supports dynamic environment workflows where environments are created and destroyed frequently.

Secret provider integration

RFC 0006 proposes secret provider integration for resolving secrets from external stores (Vault, AWS Secrets Manager, etc.). Variable sets are a natural place to reference external secrets:
The variable resolution pipeline would resolve the secret reference at evaluation time, fetching the current value from the secret provider. This keeps secrets out of the database while allowing variable sets to manage which secrets each deployment receives.

Variable set policies

Policies could enforce rules on variable sets:
  • Required variables: a policy that denies deployment if certain keys are not provided by any variable set (e.g., “all deployments must have LOG_LEVEL defined”).
  • Value constraints: a policy that validates variable values against a schema (e.g., “DATABASE_POOL_SIZE must be between 1 and 100”).
  • Sensitive enforcement: a policy that requires certain keys to be marked sensitive (e.g., any key containing PASSWORD, SECRET, or TOKEN).

Variable set drift detection

Detect and alert when variable sets that should be consistent across environments have diverged. For example, if the staging and production database variable sets should have the same keys (but different values), drift detection would flag when production has a key that staging does not. This prevents configuration gaps from reaching production.