Skip to main content

Secrets with env:// — complete example

Inject secrets from host environment variables into Loom jobs using the env:// provider. This tutorial walks through file injection, direct injection, and optional secrets in a single workflow you can copy and run.

What you will learn

  • How env:// resolves secrets from host environment variables
  • The difference between file injection (default) and direct injection (file: false)
  • How optional secrets (required: false) let jobs run when a value is absent
  • How Loom redacts secret values from console output and runtime logs

Prerequisites

  • Loom CLI installed and on your PATH
  • The environment variables referenced by env:// exported in your shell before running

Workflow

version: v1
stages: [ci]

deploy:
stage: ci
target: linux
variables:
DEPLOY_ENV: "staging"
secrets:
API_TOKEN:
ref: env://DEPLOY_API_TOKEN
DB_PASSWORD:
ref: env://DB_PASSWORD
file: false
SLACK_WEBHOOK:
ref: env://SLACK_WEBHOOK_URL
required: false
script:
- echo "Deploying to $DEPLOY_ENV"
- echo "API token file is at $API_TOKEN"
- echo "DB password is available as direct env var"
- |
if [ -n "$SLACK_WEBHOOK" ]; then
echo "Slack notification configured (file at $SLACK_WEBHOOK)"
else
echo "Slack webhook not configured, skipping"
fi

Run it

Export the secret values, then execute the workflow:

export DEPLOY_API_TOKEN="my-api-token"
export DB_PASSWORD="s3cret"
loom run --local --workflow .loom/workflow.yml

SLACK_WEBHOOK_URL is intentionally omitted — it is marked required: false, so the job runs without it and the script handles the missing case gracefully.

What each secret demonstrates

API_TOKEN — file injection (default)

API_TOKEN:
ref: env://DEPLOY_API_TOKEN
FieldDefaultBehavior
filetrueLoom writes the resolved value to a temp file with 0600 permissions and sets $API_TOKEN to the file path.
requiredtrueIf DEPLOY_API_TOKEN is not set in the host environment, the job fails with SECRETS_REQUIRED_MISSING before script execution.

Read the value in scripts with cat $API_TOKEN.

DB_PASSWORD — direct injection

DB_PASSWORD:
ref: env://DB_PASSWORD
file: false

file: false means $DB_PASSWORD contains the raw secret value, not a file path. Use direct injection when a tool reads the secret from an environment variable directly (for example, a database client that expects $DB_PASSWORD).

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

SLACK_WEBHOOK — optional secret

SLACK_WEBHOOK:
ref: env://SLACK_WEBHOOK_URL
required: false

required: false means the job continues when SLACK_WEBHOOK_URL is not set. The secret is silently omitted from injection. Your script should check whether $SLACK_WEBHOOK is set before using it.

Optional secrets are useful for notifications, telemetry, or other non-critical integrations.

Expected output

When the workflow runs successfully:

  1. The deploy job starts. Loom resolves all three secrets (or skips SLACK_WEBHOOK if absent).
  2. Console output shows the echo statements. Any line that would reveal a resolved secret value is redacted to [REDACTED:SECRET_<NAME>].
  3. Runtime logs under .loom/.runtime/logs/<run_id>/ contain the redacted output — raw values never appear in logs.

If a required secret is missing, the job fails before script execution with a SECRETS_REQUIRED_MISSING error.

Adapting this example

Add more secrets

Add entries under the secrets block. Each entry needs at minimum a ref:

secrets:
NEW_SECRET:
ref: env://MY_NEW_SECRET

Switch between file and direct injection

Change file: false to file: true (or remove file entirely — true is the default) and update scripts to read from the file:

# Direct injection (file: false)
echo "Password: $DB_PASSWORD"

# File injection (file: true, default)
echo "Password: $(cat $DB_PASSWORD)"

Use with Docker jobs

Add image: to the job. File-injected secrets are bind-mounted read-only into the container. The environment variable inside the container points to the container-local path.

The container mount path follows the pattern /tmp/loom-secret-<normalized-name>-<ordinal>, where the name is lowercased with underscores and uppercase letters replaced by dashes.

deploy:
stage: ci
target: linux
image: loom:nix-local
secrets:
API_TOKEN:
ref: env://DEPLOY_API_TOKEN
script:
- cat $API_TOKEN

Graduate to a vault provider

When you outgrow env://, switch to a vault-backed provider without changing your workflow structure — only the ref URI changes:

ProviderURI formatGuide
KeePasskeepass://<alias>#<path>:<field>KeePass secrets
1Passwordop://<vault>/<item>/<field>1Password secrets

Troubleshooting

SymptomCauseFix
SECRETS_REQUIRED_MISSINGRequired env var not exported before loom runexport VAR_NAME="value" in your shell
SECRETS_REF_INVALIDMalformed ref URIVerify the format: env://<VAR_NAME>
SECRETS_REF_NOT_FOUNDProvider resolved but the env var does not existExport the variable or set required: false
SECRETS_PROVIDER_UNAVAILABLEProvider adapter not available or misconfiguredCheck that the env:// scheme is spelled correctly
SECRETS_UNSAFE_DEBUG_TRACECI_DEBUG_TRACE=true with file: false secretsDisable debug trace or switch to file: true
Script error reading secretUsing $VAR as a raw value but file: true is setRead with cat $VAR, or set file: false

For detailed triage, follow the Diagnostics ladder and check the job's events.jsonl for the specific error event.