Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

Migrating from variables to secrets

Move sensitive values out of variables blocks and into Loom's secrets system so credentials never appear in workflow YAML, compiled output, receipts, or logs.

Time estimate: 15–30 minutes for a typical workflow.

Who should use this guide

You have an existing Loom workflow that stores sensitive values (passwords, tokens, keys) as plain-text variables. You want to migrate those values to the secrets system for automatic redaction, file-first injection, and provider-backed resolution.

If you are setting up secrets for the first time with no existing variables to migrate, start with Getting started with secrets instead.

What changes

Aspectvariables (before)secrets (after)
Value in workflow YAMLPlain textProvider reference URI only
Visible in loom compile outputYesNo
Visible in receipts and logsYesRedacted to [REDACTED:SECRET_<NAME>]
Default injection modeDirect env varTemp file path (file: true)
Provider dependencyNone1Password, KeePass, or environment passthrough

What stays the same: job structure, script blocks, stage assignments, and non-sensitive variables are unchanged. You are only moving the sensitive keys.

Prerequisites

  • Loom CLI installed (loom --version returns a version).
  • An existing workflow with sensitive values in variables blocks.
  • Access to a secrets provider: 1Password vault, KeePass database, or host environment variables.

Step 1: Identify sensitive variables

Search your workflow YAML for values that would cause damage if leaked:

  • Passwords and database connection strings
  • API tokens and bearer tokens
  • Signing keys and private keys
  • Webhook URLs containing embedded secrets

Before — credential hardcoded in variables:

deploy:
stage: ci
target: linux
variables:
DATABASE_PASSWORD: "s3cret_p@ssw0rd"
APP_ENV: "production"
script:
- ./scripts/deploy.sh

DATABASE_PASSWORD is the migration target. APP_ENV is non-sensitive and stays in variables.

Step 2: Choose a provider

ProviderBest forURI schemeAuth requirement
1PasswordTeams with centralized vault managementop://<vault>/<item>/<field>OP_SERVICE_ACCOUNT_TOKEN env var
KeePassLocal or offline encrypted storagekeepass://<alias>#<path>:<field>LOOM_KEEPASS_DB_* env vars
EnvironmentCI runners that already inject secretsenv://<VAR_NAME>Variable exported in shell

For detailed provider setup, see the provider-specific guides:

Step 3: Store values in the provider

Move each sensitive value into your chosen provider before updating the workflow. The ref URI must point to a real entry.

1Password:

export TOKEN_VALUE="s3cret_p@ssw0rd"

loom secrets op item create \
--vault Engineering \
--item-path services/loom/deploy \
--field password \
--value-from-env TOKEN_VALUE

KeePass:

export TOKEN_VALUE="s3cret_p@ssw0rd"

loom secrets keepass item create \
--item-path services/loom/deploy \
--field password \
--value-from-env TOKEN_VALUE

Environment passthrough (env://): export the variable in your shell or CI runner configuration. No storage step needed — the ref reads directly from the host environment.

Step 4: Update workflow YAML

Move each sensitive key from variables to a secrets block. Replace the plain value with a ref URI pointing to the provider entry you created.

After — credential referenced through a provider:

deploy:
stage: ci
target: linux
variables:
APP_ENV: "production"
secrets:
DATABASE_PASSWORD:
ref: op://Engineering/services/loom/deploy/password
script:
- ./scripts/deploy.sh

A key cannot appear in both variables and secrets on the same job. Remove the key from variables before adding it to secrets.

Step 5: Update scripts for file injection

The default injection mode is file: true. After migration, the environment variable holds a file path instead of the raw value. Update any script that reads the variable directly:

# Before (direct value from variables)
curl -u "admin:${DATABASE_PASSWORD}" https://db.example.com/health

# After (file-injected secret — read with cat)
curl -u "admin:$(cat "$DATABASE_PASSWORD")" https://db.example.com/health

If your tooling requires the raw value in the environment variable and cannot read from a file, set file: false:

secrets:
NPM_TOKEN:
ref: env://NPM_TOKEN
file: false

Tradeoff: direct injection (file: false) is more exposed to shell tracing (set -x). If CI_DEBUG_TRACE=true is set and any secret uses file: false, Loom hard-fails with SECRETS_UNSAFE_DEBUG_TRACE.

Step 6: Validate and run

loom check
loom run --local --workflow .loom/workflow.yml

loom check catches schema violations before execution: key collisions between variables and secrets, invalid ref URIs, and naming errors.

During the run, confirm:

  • The job starts and the secret resolves without SECRETS_* errors.
  • Console output shows the file path (e.g. /tmp/loom-secret-DATABASE_PASSWORD-0), not the raw value.
  • Any accidental exposure of the secret value is redacted to [REDACTED:SECRET_DATABASE_PASSWORD].

Common pitfalls

MistakeSymptomFix
Same key in variables and secretsSchema validation error at loom checkRemove the key from variables
Script reads $VAR directly after migrationScript receives a file path instead of a valueUse cat "$VAR" or set file: false
Provider auth not configuredSECRETS_PROVIDER_UNAVAILABLE at runtimeExport OP_SERVICE_ACCOUNT_TOKEN or LOOM_KEEPASS_DB_* env vars
Typo in ref URISECRETS_REF_INVALID or SECRETS_REF_NOT_FOUNDVerify URI against loom secrets op item list or loom secrets keepass item list
CI_DEBUG_TRACE=true with file: falseSECRETS_UNSAFE_DEBUG_TRACEDisable debug trace or switch secret to file: true

Migration checklist

  • Sensitive values identified in workflow YAML
  • Values stored in a secrets provider (1Password, KeePass, or env)
  • variables entries replaced with secrets entries and ref URIs
  • Scripts updated to handle file-injected values (cat "$VAR")
  • loom check passes
  • loom run --local succeeds with secrets resolved and output redacted
  • No plain-text credentials remain in workflow files