▄▖ ▗ ▌ ▄▖ ▗ ▖▖
▙▖▛▌▛▘▛▘▌▌▛▌▜▘█▌▛▌ ▚ █▌▛▘▛▘█▌▜▘▛▘ ▙▘█▌█▌▛▌█▌▛▘
▙▖▌▌▙▖▌ ▙▌▙▌▐▖▙▖▙▌ ▄▌▙▖▙▖▌ ▙▖▐▖▄▌ ▌▌▙▖▙▖▙▌▙▖▌
▄▌▌ ▌
ESK is an encrypted secrets manager that lets you define secrets once and deploy them to many targets.
It is built for teams that want:
- A local encrypted source of truth
- Simple deploys to local files and cloud platforms
- Optional sync/backup with shared secret backends
What esk does
- Stores secrets in
.esk/store.enc(AES-256-GCM encrypted) - Keeps the decryption key locally (file or OS keychain)
- Deploys to targets like
.envfiles, Cloudflare, Convex, Vercel, GitHub Actions, Kubernetes, Docker Swarm, and more - Syncs with remotes like 1Password, cloud folders, AWS Secrets Manager, Vault, Bitwarden, S3, GCP, Azure, Doppler, and SOPS
- Validates values against format, pattern, enum, and range constraints
- Audits required secrets before deploy — catches missing values early
- Detects empty/whitespace-only values that break runtime defaults
- Generates TypeScript declarations, runtime validators, and
.env.exampletemplates - Prunes orphaned deploys (secrets removed from config but still deployed to targets)
Install
Shell script (Linux/macOS)
|
Cargo
From source
60-second quick start
- Initialize a project.
- Add your first secret.
- Add more secrets without syncing or deploying on each write, then deploy once.
- Verify status.
esk init creates:
| File | Purpose | Commit to git |
|---|---|---|
esk.yaml |
Project config (environments, apps, targets, remotes, secrets, generate) | Yes |
.esk/store.enc |
Encrypted secret store | Yes |
.esk/store.key |
Local encryption key (32-byte hex); or stored in OS keychain | No |
.esk/deploy-index.json |
Deploy state tracker | No (gitignored) |
.esk/sync-index.json |
Sync state tracker | No (gitignored) |
Mental model
esk has 3 parts:
- Store: local encrypted data (
.esk/store.enc+ local key) - Targets: deploy secrets to runtime services (
esk deploy) - Remotes: sync full secret state to team/shared backends (
esk sync)
Important default behavior
By default, esk set and esk delete do more than update local storage:
- Update encrypted local store
- Push to configured remotes
- Deploy to configured targets
Use --no-sync to skip steps 2 and 3. Use --strict to fail before deploy if any remote push fails.
Minimal config (esk.yaml)
Start with local .env deploy only:
project: myapp
environments:
apps:
web:
path: .
targets:
.env:
pattern: "{app_path}/.env{env_suffix}.local"
env_suffix:
dev: ""
prod: ".production"
secrets:
General:
API_KEY:
description: Example API key
targets:
.env:
When you need cloud deploy targets or shared sync, add target/remote blocks. See TARGETS.md and REMOTES.md, or browse the full example config showcasing every available option.
Commands you will use most
| Command | Purpose |
|---|---|
esk init |
Initialize config and encrypted store |
esk set <KEY> --env <ENV> |
Set a secret (auto-sync/deploy by default) |
esk get <KEY> --env <ENV> |
Read a secret |
esk delete <KEY> --env <ENV> |
Delete a secret (auto-sync/deploy by default) |
esk list [--env <ENV>] |
List secrets and deploy status |
esk deploy [--env <ENV>] |
Deploy to configured targets |
esk status [--env <ENV>] |
Show drift/sync dashboard |
esk sync [--env <ENV>] |
Pull, reconcile, and push remote state |
esk generate [<FORMAT>] |
Generate code/config from secret definitions |
Full flags and behavior: API.md.
Supported deploy targets
.env* filesaws_lambdaaws_ssmazure_app_servicecirclecicloudflareconvexdockerflygcp_cloud_rungithubgitlabherokukubernetesnetlifyrailwayrendersupabasevercel- Custom targets — define your own deploy commands in
esk.yaml
Target config details: TARGETS.md.
Supported sync remotes
1passwordaws_secrets_managerazurebitwarden- Cloud storage (
dropbox,gdrive,onedrive, etc.) dopplergcpinfisicals3sopsvault
Remote config details: REMOTES.md.
Security model
- Encryption: AES-256-GCM with a random nonce for every write
- Key isolation:
.esk/store.keystays local and must not be committed - Tamper resistance: authenticated encryption
- Reliability: atomic writes for store and index files
The encrypted store file is safe to commit. The key file is not.
Key storage
The encryption key can be stored in two ways:
| Provider | How | When to use |
|---|---|---|
| File (default) | .esk/store.key, gitignored |
Works everywhere, including CI and headless |
| OS keychain | macOS Keychain, Windows Credential Manager, Linux Secret Service | Interactive workstations; hardware-backed on macOS/Windows |
Initialize with esk init (file) or esk init --keychain (keychain). On supported platforms (macOS, Windows, Linux with Secret Service), esk init will prompt to choose.
Why not 1Password, Bitwarden, or other password managers? The encryption key is read on every esk command. It must be local, instant, and available offline. Password managers require network access and interactive auth, making them unsuitable as a key provider. They also create a circular dependency: esk uses these services as sync remotes for the encrypted store, so the key that decrypts the store cannot itself depend on reaching those services.
For team key distribution, share the key out-of-band (paste it into a shared vault, send via a secure channel). The encrypted store is then shared via remotes as usual.
Quick troubleshooting
esk.yaml not found: run commands from your project root, or runesk initencryption key not found: runesk initto create.esk/store.key, oresk init --keychainfor OS keychain- Target/remote CLI errors: install and authenticate required CLIs (for example
wrangler,op,aws) - Unknown environment/app in target: verify names match
environmentsandappsinesk.yaml
MCP server
esk includes an MCP (Model Context Protocol) server that exposes secret operations as structured tools over stdio. Any MCP-compatible client can use it — Claude Code, Claude Desktop, Cursor, Zed, etc.
Build:
# or from source
Configure (example for Claude Code ~/.claude/settings.json):
Available tools:
| Tool | Description |
|---|---|
esk_get |
Retrieve a secret value |
esk_set |
Set a secret value (no auto-deploy) |
esk_delete |
Delete a secret value (no auto-deploy) |
esk_list |
List secrets with deploy status per environment |
esk_status |
Project health: drift, warnings, next steps |
esk_deploy |
Deploy secrets to configured targets |
esk_generate |
Generate TypeScript declarations, .env.example |
The MCP binary is feature-gated behind mcp to keep the main CLI binary lean.
Development
cargo xtask sandbox builds a release binary and scaffolds a test project in /private/tmp/esk-test with mock CLI shims and sample secrets.
Release from Cargo.toml version in one command:
This command:
- verifies you are on
mainand your working tree is clean - reads the crate version and checks the tag doesn't already exist
- pulls with rebase from origin
- runs
fmt --check,clippy, andtest - pushes
main, then creates and pushes thev<version>tag
Preview without changes: