Local Secrets

The local vault is the foundation of lpm env. It works without an account, without network access, on every plan. Values live in your OS keychain (Keychain on macOS, Secret Service on Linux, Credential Manager on Windows) and are scoped per project — moving or renaming the directory does not lose the vault, because each project gets a stable opaque vault-id written on first use.

Quick reference

lpm env set DATABASE_URL=postgres://...    # write one
lpm env set FOO=bar BAZ=qux                # write many
lpm env get DATABASE_URL                   # read (masked)
lpm env get DATABASE_URL --reveal          # read (plaintext)
lpm env list                               # all keys (values masked)
lpm env list --reveal                      # all keys + values
lpm env delete FOO BAZ                     # remove one or more
lpm env import .env.production             # import from a file (skips existing)
lpm env import .env --overwrite            # import and overwrite
lpm env export .env.backup                 # export to a file
lpm env print                              # print for `eval $(lpm env print)`

Every read of a value defaults to masked output (DATABASE_URL=********). Pass --reveal to print the plaintext. The mask is friction by design — it stops accidental terminal scrollback or screen-share leaks.

Setting values

set accepts one or many KEY=VALUE pairs. Quoting rules follow your shell:

lpm env set API_KEY=sk-1234
lpm env set "GREETING=hello world"
lpm env set MULTI_LINE="$(cat private.pem)"
lpm env set FLAG_A=1 FLAG_B=2 FLAG_C=3

Setting an existing key overwrites it. To prevent that, use lpm env diff first or use a write-once import workflow with lpm env import (without --overwrite).

Reading values

lpm env get STRIPE_KEY                     # STRIPE_KEY=*********
lpm env get STRIPE_KEY --reveal            # STRIPE_KEY=sk_test_abc...
lpm env list                               # all keys, all masked
lpm env list --json                        # JSON output for piping

To feed values to a shell, use lpm env print:

eval "$(lpm env print)"            # export current vars into the shell
lpm env print --env=staging > .env.staging   # write to a file

lpm run <script> and lpm dev inject the resolved environment automatically — you rarely need eval in practice.

Import / export

lpm env import .env                        # adds keys not already in the vault
lpm env import .env --overwrite            # replaces existing keys
lpm env import .env.production --env=production    # scoped import
lpm env export .env.backup                 # write all keys + values to file
lpm env export .env --env=staging          # export one environment

Import understands the .env format (KEY=VALUE, # comments, quoted values). Export writes a plain .env file with plaintext values — treat the output the way you would any other secret. Pipe it to a co-worker through an encrypted channel; don't commit it.

Schema validation

If your project has an envSchema in lpm.json, lpm env check validates the local vault against it:

{
	"envSchema": {
		"DATABASE_URL": { "required": true, "format": "url", "secret": true },
		"PORT": { "required": false, "default": "3000", "pattern": "^\\d+$" },
		"API_KEY": { "required": true, "secret": true }
	}
}
lpm env check                              # validate local
lpm env example                            # generate .env.example from the schema

check reports missing required keys, format mismatches, and pattern mismatches without revealing values. example writes a .env.example you can commit — keys only, no values.

lpm dev and lpm run honor the schema by default; pass --no-env-check to skip.

Where the data actually lives

macOS              → Keychain item (service: dev.lpm.vault, account: <vault-id>)
Linux              → libsecret / GNOME Keyring (same service / account)
Windows            → Credential Manager (same service / account)
File fallback      → ~/.lpm/.vault-data/<vault-id>.enc  (mode 0o600, AES-encrypted)

The file fallback is automatic when no keychain is available (CI runners, headless servers, sandboxed environments). Force it for testing:

LPM_FORCE_FILE_VAULT=1 lpm env list

Initial setup

For a guided walkthrough that asks what environments you want and seeds a starter schema:

lpm env init                               # interactive setup
lpm env ls                                 # show environments + counts

Listing environments and copying between them

lpm env ls                                 # overview table
lpm env copy default staging               # copy all keys from default → staging
lpm env copy staging production --env-rename --prefix STAGING_   # selective copy

The copy (alias cp) form copies an entire environment in one shot, which is faster than export | import. See Per-Environment Scoping for environment-level operations.

Next