| Category | Status | Created | Author |
|---|---|---|---|
| Variables | Draft | 2026-03-13 | Justin Brooks |
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 needDATABASE_URL,
REDIS_URL, LOG_LEVEL, and REGION. The operator must:
- Create 4 deployment variables on each of the 15 deployments (60 variables).
- For each variable, create deployment variable values with the correct resource selectors to differentiate production from staging.
- When the staging database endpoint rotates, update the value across all 15 deployments individually.
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_URLdeployment 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
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:- Resource variable — a variable defined directly on the resource with a matching key. This is the most specific override and always wins.
- Deployment variable value — a deployment variable value whose resource selector matches the target resource, sorted by priority (highest first).
- Variable set (environment scope) — a variable set scoped to the specific environment in the release target, sorted by set priority.
- Variable set (system scope) — a variable set scoped to the system containing the deployment, sorted by set priority.
- Variable set (workspace scope) — a variable set scoped to the workspace, sorted by set priority.
- Deployment variable default — the default value defined on the deployment variable.
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
TheManager.Evaluate method in the workspace-engine is extended to query
variable sets after deployment variable values and before deployment variable
defaults:
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.
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
tRPC
Terraform provider
UI
Variable sets management page
A new page at/workspaces/{id}/settings/variable-sets lists all variable sets
in the workspace:
| Name | Scope | Target | Variables | Priority |
|---|---|---|---|---|
| production-database | Environment | production | 3 | 10 |
| staging-database | Environment | staging | 3 | 10 |
| shared-feature-flags | Workspace | All deployments | 8 | 0 |
| payment-system-config | System | payment | 5 | 5 |
- 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:| Variable | Value | Source |
|---|---|---|
| DATABASE_URL | postgres://prod-db.internal:5432/… | Variable Set: production-db |
| REPLICA_COUNT | 5 | Deployment Variable Value |
| LOG_LEVEL | info | Variable Set: defaults |
| FEATURE_NEW_UI | true | Resource Variable |
| CACHE_TTL | 300 | Deployment Variable Default |
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: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: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):Layered overrides
Multiple variable sets at different scopes can coexist. The resolution chain handles precedence naturally: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:gpu_enabled: true in metadata
receive these variables. Other release targets are unaffected.
Migration
- The
variable_setandvariable_set_variabletables 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
-
Variable set assignment vs. scope-based matching. The current proposal
uses scope-based matching: a variable set with
scope = environmentandscope_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? - 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?
-
Sensitive variable handling. The
sensitiveflag 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? - 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.”
- 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.
- 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.
- 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.
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: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_LEVELdefined”). - Value constraints: a policy that validates variable values against a
schema (e.g., “
DATABASE_POOL_SIZEmust be between 1 and 100”). - Sensitive enforcement: a policy that requires certain keys to be marked
sensitive (e.g., any key containing
PASSWORD,SECRET, orTOKEN).