Skip to the content.

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:

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:

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:

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:

  1. --org flag (highest priority, overrides everything)
  2. ~/.lusterpass/org (cached during lusterpass login)
  3. Error with guidance to use --org or run lusterpass 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:

By value pattern — these prefixes/formats are flagged:

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