Migration Guide: From Plain .envrc to lusterpass
This guide walks through migrating an existing .envrc (or any shell rc file with export lines) to lusterpass’s encrypted secret management.
Before You Start
Prerequisites:
lusterpass logincompleted (access token + org ID saved)- Bitwarden projects set up (see bitwarden-setup.md)
During lusterpass login, you’ll be prompted for your org ID. Once saved, all commands use it automatically — no need to pass --org every time.
Example: Migrating a Typical .envrc
Say you have this .envrc in your project:
# .envrc — everything in plain text (bad!)
export APP_NAME=webapp
export APP_URL=http://localhost:8080
export LOG_LEVEL=debug
export LOG_FORMAT=json
export TZ=UTC
export PORT=8080
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=webapp_dev
export DB_PASSWORD="p@ssw0rd!123"
export REDIS_URL=redis://localhost:6379
export OPENAI_API_KEY='sk-proj-EXAMPLE'
export STRIPE_SECRET_KEY='sk_test_EXAMPLE'
export AWS_ACCESS_KEY_ID=AKIA_EXAMPLE_KEY_ID
export AWS_SECRET_ACCESS_KEY='EXAMPLE_AWS_SECRET_ACCESS_KEY'
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export JWT_SECRET='my-super-secret-jwt-signing-key-2024!@#'
export SMTP_PASSWORD="mailgun-smtp-pass-123"
export SENTRY_DSN=https://abc123@o456.ingest.sentry.io/789
eval $(direnv hook zsh)
Step 1: Run migrate
lusterpass migrate .envrc
The --project flag defaults to the current directory name. Only specify it if you want a different name:
# Explicit project name (only needed if different from directory name)
lusterpass migrate .envrc --project webapp
Output:
Migration analysis complete:
Source: .envrc
Plain vars: 10
Secrets: 9
Skipped: 1 (non-export lines)
Generated files:
.lusterpass.yaml — lusterpass config (review and commit to git)
onboard-secrets.sh — secret onboarding script (edit, then run)
This produces two files.
Step 2: Review .lusterpass.yaml
The generated config separates plain vars from secrets automatically:
# Generated by: lusterpass migrate
# This file contains NO secret values — safe to commit to git.
project: webapp
# Common: shared across all environments
common:
vars:
APP_NAME: webapp
APP_URL: "http://localhost:8080"
DB_HOST: localhost
DB_NAME: webapp_dev
DB_PORT: 5432
LOG_FORMAT: json
LOG_LEVEL: debug
PORT: 8080
REDIS_URL: "redis://localhost:6379"
TZ: UTC
secrets:
# Format: ENV_VAR_NAME: bitwarden-reference-name
AWS_ACCESS_KEY_ID: aws-access-key-id--webapp
AWS_SECRET_ACCESS_KEY: aws-secret-access-key--webapp
DB_PASSWORD: db-password--webapp
GITHUB_TOKEN: github-token--webapp
JWT_SECRET: jwt-secret--webapp
OPENAI_API_KEY: openai-api-key--webapp
SENTRY_DSN: sentry-dsn--webapp
SMTP_PASSWORD: smtp-password--webapp
STRIPE_SECRET_KEY: stripe-secret-key--webapp
# Per-environment profiles
profiles:
dev:
vars:
# Example: override common vars for dev
# LOG_LEVEL: debug
# secrets:
# Example: per-env secret overrides
# AWS_ACCESS_KEY_ID: aws-access-key-id--webapp--dev
# DB_PASSWORD: db-password--webapp--dev
# ...
What to check:
- Are all secrets correctly identified? Move any misclassified entries between
varsandsecrets. - Review reference names — the convention is
<purpose>--<project>. Adjust if needed. - Move env-specific vars (like
DB_HOST,DB_NAME) from common into profiles if they differ per environment.
Step 3: Review onboard-secrets.sh
The generated script contains your actual secret values and the lusterpass enrol commands to upload them:
#!/usr/bin/env bash
set -euo pipefail
# Usage:
# chmod +x onboard-secrets.sh
# ./onboard-secrets.sh --org YOUR_ORG_ID --project credentials
enrol_secret() {
local ref="$1" value="$2" note="${3:-}"
echo " Enrolling: $ref"
lusterpass enrol --org "$ORG_ID" --ref "$ref" --value "$value" \
--note "$note" --project-name "$BW_PROJECT"
}
# Each secret is listed with its reference name and value:
enrol_secret 'db-password--webapp' 'p@ssw0rd!123' 'Migrated from .envrc: DB_PASSWORD'
enrol_secret 'openai-api-key--webapp' 'sk-proj-EXAMPLE' 'Migrated from .envrc: OPENAI_API_KEY'
enrol_secret 'stripe-secret-key--webapp' 'sk_test_EXAMPLE' 'Migrated from .envrc: STRIPE_SECRET_KEY'
# ... (all detected secrets)
What to edit:
- Change
--refnames to match your naming convention if needed - Remove secrets that already exist in Bitwarden
- Change
--projectfromcredentialsto another project if appropriate (e.g.,certificatesfor TLS keys)
Step 4: Run the onboarding script
If you saved your org ID during lusterpass login, the script uses it automatically:
chmod +x onboard-secrets.sh
./onboard-secrets.sh
Or pass --org to override the cached org ID for this run:
./onboard-secrets.sh --org a1e4a796-78c7-41c7-8d7c-b40a00cf6392 --project credentials
Output:
Onboarding secrets to Bitwarden...
Enrolling: aws-access-key-id--webapp
[1/9]
Enrolling: aws-secret-access-key--webapp
[2/9]
...
Enrolling: stripe-secret-key--webapp
[9/9]
Done! 9 secrets enrolled.
Step 5: Pull and verify
lusterpass pull --profile dev
lusterpass env --profile dev
Expected output:
export APP_NAME='webapp'
export APP_URL='http://localhost:8080'
export AWS_ACCESS_KEY_ID='AKIA_EXAMPLE_KEY_ID'
export AWS_SECRET_ACCESS_KEY='EXAMPLE_AWS_SECRET_ACCESS_KEY'
export DB_HOST='localhost'
export DB_NAME='webapp_dev'
export DB_PASSWORD='p@ssw0rd!123'
...
Step 6: Replace your .envrc
Replace the old plain-text .envrc with:
# .envrc — secrets loaded from Bitwarden via lusterpass
eval $(lusterpass env --profile dev)
Step 7: Clean up
# DELETE the onboarding script — it contains plain secret values!
rm onboard-secrets.sh
# Commit the safe config file
git add .lusterpass.yaml
git commit -m "feat: migrate to lusterpass secret management"
Org ID Resolution
All commands that need the Bitwarden org ID resolve it in this order:
--orgflag (highest priority, overrides everything)~/.lusterpass/org(cached duringlusterpass login)- Error with guidance to use
--orgor runlusterpass login
This means after lusterpass login, you never need --org again:
# Before: had to pass --org every time
lusterpass pull --profile dev --org a1e4a796-...
lusterpass list --org a1e4a796-...
# After: org ID is cached, just use the command
lusterpass pull --profile dev
lusterpass list
# Override for a one-off call to a different org
lusterpass list --org different-org-id-here
How Secret Detection Works
The migrate command classifies each export KEY=VALUE line as either a plain var or a secret using two approaches:
By key name — these patterns are flagged as secrets:
PASSWORD,PASSWD,PASSSECRET,TOKEN,AUTHAPI_KEY,APIKEYPRIVATE_KEY,ACCESS_KEY,SECRET_KEY,SIGNING_KEY,ENCRYPTION_KEYCREDENTIALSCONNECTION_STRING,CONN_STRDSN,DATABASE_URL
By value pattern — these prefixes/formats are flagged:
sk-(OpenAI, Stripe)ghp_,gho_,github_pat_(GitHub tokens)xoxb-,xoxp-(Slack tokens)AKIA(AWS access keys)eyJ(JWT tokens)- Long base64 or hex strings
- 20+ char strings with mixed character classes (excluding URLs, paths, emails)
Non-export lines (comments, eval, blank lines) are skipped.
Options
lusterpass migrate [file] [flags]
Flags:
--project string Project name for reference naming (default: current directory name)
--out string Output directory for generated files (default: current directory)
Tips
- Multiple source files: Run migrate on each file, merge the outputs manually.
- Per-env secrets: After migration, uncomment and adjust the profile secret overrides in
.lusterpass.yamlif different environments use different API keys. - Shared secrets: Secrets that are the same across all environments stay in
common.secrets. Only put env-specific overrides underprofiles.<env>.secrets. - Dry run: The migrate command never modifies your original file or touches Bitwarden. It only generates new files.