Separate config from secrets in .env files. Secrets stay in your OS keychain (or other backends) — the .env file holds only ref:// references, making it safe to commit.
.env = config values + ref:// secret references (committed to git)
secrets = OS keychain / password manager / cloud vault
envref resolve = config + resolved secrets → direnv / shell env
brew install xcke/tap/envrefgo install github.com/xcke/envref/cmd/envref@latestDownload the latest binary from GitHub Releases for your platform (Linux, macOS, Windows; amd64/arm64).
git clone https://github.com/xcke/envref.git
cd envref
make build
# Binary is at ./build/envrefcd my-project
envref init --project my-appThis creates:
.envref.yaml— project config (project name, secret backends).env— environment variables with example entries.env.local— local overrides (gitignored)
# .env — safe to commit
APP_NAME=my-app
APP_PORT=3000
DATABASE_URL=ref://secrets/database_url
API_KEY=ref://secrets/api_keyenvref secret set database_url
# Prompts for the value (hidden input)
envref secret set api_key --value sk-abc123
# Non-interactive mode# Print resolved KEY=VALUE pairs
envref resolve
# Inject into a command
envref run -- node server.js
# Use with direnv
envref init --direnv
# This generates .envrc with: eval "$(envref resolve --direnv)"envref uses a layered merge strategy:
.env ← .env.<profile> ← .env.local
.envis your base config — committed to git, containsref://references for secrets.env.<profile>(optional) overrides per environment (development, staging, production).env.localis your personal overrides — gitignored, never committed
During resolution, ref:// URIs are resolved through configured secret backends (OS keychain by default). Variable interpolation (${VAR}) is supported within values.
The core of envref is the resolve pipeline. When you run envref resolve, this is what happens:
envref resolve --profile staging
|
+---------------v----------------+
| Load & Merge .env |
| |
| .env (base, committed) |
| <- .env.staging (profile) |
| <- .env.local (personal) |
| |
| Later files override earlier |
+---------------+----------------+
|
+---------------v----------------+
| Variable Interpolation |
| |
| DB_URL=postgres://${DB_HOST} |
| -> postgres://localhost |
+---------------+----------------+
|
+---------v---------+
| Has ref:// values? |
+---------+---------+
yes | | no
+-----------+ +-------> output as-is
|
+-----------v-------------+
| Reference Resolution |
| |
| ref://secrets/api_key |
| | |
| v |
| Parse URI -> backend |
| | |
| v |
| Backend Lookup |
| (with caching) |
+---+--------+--------+--+
| | |
+-------v--+ +---v----+ +-v--------+
| Keychain | | Vault | | (future) |
| (OS) | | (local)| | 1P / SSM |
+----------+ +--------+ +----------+
| | |
+--------v--------+
|
+------------v-----------+
| Format & Output |
| |
| plain: KEY=VALUE |
| shell: export KEY=VAL |
| json: [{key, value}] |
| table: bordered |
+------------------------+
Backends are tried in the order defined in .envref.yaml. The first backend that has the key wins. Each secret is namespaced by project (and optionally by profile) to prevent collisions:
Backend key format:
Without profile: <project>/<key> e.g. my-app/api_key
With profile: <project>/<profile>/<key> e.g. my-app/staging/api_key
Lookup order (with --profile staging):
1. my-app/staging/api_key <- profile-scoped (tried first)
2. my-app/api_key <- project-scoped (fallback)
Seven backend types are supported (two built-in, four via CLI wrappers, plus a plugin system):
| Backend | Type | Storage | Use case |
|---|---|---|---|
| OS Keychain | keychain |
macOS Keychain / Linux Secret Service / Windows Credential Manager | Default — zero setup |
| Local Vault | vault |
SQLite + age encryption | Headless servers, containers, CI |
| 1Password | 1password |
1Password vault via op CLI |
Teams using 1Password |
| AWS SSM | aws-ssm |
AWS Systems Manager Parameter Store | AWS infrastructure |
| HashiCorp Vault | hashicorp-vault |
Vault KV v2 secrets engine | Enterprise secret management |
| OCI Vault | oci-vault |
Oracle Cloud Infrastructure Vault | Oracle Cloud workloads |
| Plugin | plugin |
Custom external executable | Any secret store via JSON protocol |
See docs/secret-backends.md for detailed configuration and examples.
cmd/envref/ Entry point (minimal main.go)
internal/
cmd/ CLI commands (Cobra)
parser/ .env file lexer (quotes, multiline, BOM, CRLF)
envfile/ Env container, merge, interpolation
ref/ ref:// URI parser
resolve/ Reference resolution pipeline
backend/ Backend interface + 7 backend implementations
config/ .envref.yaml loader (Viper)
schema/ .env.schema.json validator
suggest/ Fuzzy key matching (Levenshtein)
output/ Verbosity-aware writer + color
| Command | Description |
|---|---|
envref init |
Scaffold a new envref project |
envref get <KEY> |
Print the value of an environment variable |
envref set <KEY>=<VALUE> |
Set a variable in a .env file |
envref list |
List all environment variables |
envref resolve |
Resolve all references and output KEY=VALUE pairs |
envref run -- <cmd> |
Run a command with resolved env vars injected |
envref secret set|get|delete|list |
Manage secrets in backends |
envref secret generate <key> |
Generate and store a random secret |
envref secret copy <key> --from <project> |
Copy a secret between projects |
envref profile list|use|create|diff |
Manage environment profiles |
envref validate |
Check .env against .env.example schema |
envref status |
Show environment overview with actionable hints |
envref doctor |
Scan .env files for common issues |
envref config show |
Print resolved effective config |
envref edit |
Open .env files in your editor |
envref completion <shell> |
Generate shell completion scripts |
envref version |
Print the version |
Profiles let you maintain per-environment configs:
# Create a staging profile
envref profile create staging
# Switch to it
envref profile use staging
# Resolve with a specific profile
envref resolve --profile production
# Compare two profiles
envref profile diff staging productionProfile-scoped secrets are supported — envref secret set db_pass --profile staging stores the secret under <project>/staging/db_pass, separate from the default scope.
Check your .env against .env.example to catch missing variables:
envref validateFor type-level validation, create a .env.schema.json:
{
"APP_PORT": { "type": "port", "required": true },
"API_URL": { "type": "url", "required": true },
"DEBUG": { "type": "boolean" },
"LOG_LEVEL": { "type": "enum", "values": ["debug", "info", "warn", "error"] }
}envref validate --schemaUse --ci in pipelines for exit code 1 on failure:
envref validate --cienvref integrates with direnv so your environment is automatically resolved when you cd into a project:
envref init --direnv
direnv allowThis generates an .envrc that runs eval "$(envref resolve --direnv)" on directory entry.
For environments without OS keychain access (headless servers, containers), envref includes a local encrypted vault:
# Initialize with a master passphrase
envref vault init
# Use vault as the backend
# (set secret_backend: vault in .envref.yaml)
# Lock vault to prevent access
envref vault lock
# Unlock to restore access
envref vault unlockThe vault stores each secret individually encrypted with age scrypt in a local SQLite database. The passphrase can be provided interactively, via the ENVREF_VAULT_PASSPHRASE environment variable, or in config.
| Flag | Description |
|---|---|
--quiet, -q |
Suppress informational output (errors only) |
--verbose |
Show additional detail |
--debug |
Show debug information |
--no-color |
Disable colorized output (also respects NO_COLOR env var) |
Project config lives in .envref.yaml:
project: my-app
secret_backend: keychain
profiles:
- development
- staging
- production
active_profile: developmentGlobal defaults can be set at ~/.config/envref/config.yaml — project config takes precedence.
Requires Go 1.24+.
make build # Compile to ./build/envref
make test # Run tests with race detector
make lint # Run golangci-lint
make check # vet + lint + test
make install # Install to $GOPATH/bin
make cover # Run tests with coverage reporting
make cover-html # Generate HTML coverage reportgo test -bench=. -benchmem ./internal/parser/ ./internal/resolve/ ./internal/envfile/The resolve pipeline is optimized for <50ms startup with 100 variables to support direnv integration where envref resolve runs on every cd.
MIT — see LICENSE for details.