Variables and secrets

How variables layer from organization to service, how secrets are stored, and how they reach a running service.

Variables are the environment a service runs with. You set them in layers, and each key takes its value from the most specific layer that sets it.

Layers

From least to most specific:

  1. Organization, every environment
  2. Organization, one environment
  3. Project, every environment
  4. Project, one environment
  5. Service, every environment
  6. Service, one environment

So a service-level LOG_LEVEL for production beats a project-wide one, which beats the organization's. Setting a key that a layer already has for that environment is an error, so edit the existing one instead.

Keys start with a letter or underscore and hold letters, digits and underscores. Organization-wide variables need the admin role. Project and service variables need developer or above.

Set them

loomctl vars my-project
loomctl vars set my-project LOG_LEVEL=debug --env preview
loomctl vars set my-project STRIPE_KEY=- --secret --env production
loomctl vars set my-project FEATURE_X=on --service svc:api:go:cmd/server

KEY=- reads the value from standard input, which keeps a secret out of your shell history. --env limits a variable to one environment, and --service limits it to one service by its graph ID. In the dashboard, the project's Variables tab does the same, and the organization's settings hold the organization-wide layer.

Secrets

Mark a variable secret and its value is write-only. Loomplane encrypts it in Vault (AES-256-GCM, versioned) and the API never returns it, to anyone, whatever their role. A database constraint keeps secret values out of the variables table entirely.

When a service deploys, its secrets go to the cloud's own secret store and reach the service by reference, readable by that service's runtime identity only:

TargetWhere secrets go
Cloud RunSecret Manager
ECS on FargateSecrets Manager
VercelSensitive environment variables
DigitalOcean App PlatformApp Platform secrets
Azure Container AppsApp secrets

They never pass through Ctrl Plane, which stores what it's given as plain JSON. A secret never appears in a task definition, a list response or the CLI's output, and the integration tests check that.

Addresses Loomplane adds

A service gets a variable for each thing it needs. Resource addresses hold credentials, so they're secrets and arrive through the secret store.

NeedsVariable
Postgres, MySQL, MariaDBDATABASE_URL
Redis, ValkeyREDIS_URL
MemcachedMEMCACHED_SERVERS
MongoDBMONGODB_URI
AMQP brokersAMQP_URL
KafkaKAFKA_BROKERS
NATSNATS_URL
Object storageSTORAGE_URL
SearchSEARCH_URL
ClickHouseCLICKHOUSE_URL
Another serviceIts name in capitals plus _URL, such as API_URL

When two needs would share a name, each gets its own name's instead, such as ANALYTICS_URL. A variable you set yourself wins over a generated one.

MariaDB and Valkey resources used to get their name in capitals plus _URL (DB_URL). A service deployed that way keeps the old variable beside the new one, and the plan warns you to move off it.

The secret key

Every key Loomplane needs is derived from LOOMPLANE_SECRET_KEY with HKDF under a label of its own. That includes Vault's encryption, authsome's tokens and the GitHub install state's signature. Keep the secret key out of your repository and out of your backups. Losing it loses every credential and secret; leaking it along with a database copy exposes them. The security model has the whole picture.

On this page