▄▖ ▗ ▌ ▄▖ ▗ ▖▖
▙▖▛▌▛▘▛▘▌▌▛▌▜▘█▌▛▌ ▚ █▌▛▘▛▘█▌▜▘▛▘ ▙▘█▌█▌▛▌█▌▛▘
▙▖▌▌▙▖▌ ▙▌▙▌▐▖▙▖▙▌ ▄▌▙▖▙▖▌ ▙▖▐▖▄▌ ▌▌▙▖▙▖▙▌▙▖▌
▄▌▌ ▌
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
- 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, Zod schemas, and
.env.exampletemplates - Prunes orphaned deploys (secrets removed from config but still deployed to targets)
Shell script (Linux/macOS)
curl -fsSL https://raw.githubusercontent.com/thomastheyoung/esk/main/install.sh | bashCargo
cargo install esk
cargo binstall eskFrom source
git clone https://github.com/thomastheyoung/esk.git
cd esk
cargo build --release- Initialize a project.
esk init- Add your first secret.
esk set API_KEY --env dev --group General- Add more secrets without syncing or deploying on each write, then deploy once.
esk set DATABASE_URL --env dev --group General --no-sync
esk deploy --env dev- Verify status.
esk list --env dev
esk status --env devesk 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/key-provider |
Records key storage method (file or keychain) |
No (gitignored) |
.esk/deploy-index.json |
Deploy state tracker | No (gitignored) |
.esk/sync-index.json |
Sync state tracker | No (gitignored) |
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)
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.
Start with local .env deploy only:
project: myapp
environments: [dev, prod]
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: [web:dev, web:prod]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.
| 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 |
esk doctor |
Diagnose project health in one pass |
Full flags and behavior: API.md.
.env* filesaws_lambdaaws_ssmazure_app_servicecirclecicloudflareconvexdockerflygcp_cloud_rungithubgitlabherokukubernetesnetlifyrailwayrendersupabasevercel- Custom targets — define your own deploy commands in
esk.yaml
Target config details: TARGETS.md.
1passwordaws_secrets_managerazurebitwarden- Cloud storage (
dropbox,gdrive,onedrive, etc.) dopplergcpinfisicals3sopsvault
Remote config details: REMOTES.md.
- 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.
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.
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
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:
cargo install esk --features mcp
# or from source
cargo build --release --features mcpConfigure (example for Claude Code ~/.claude/settings.json):
{
"mcpServers": {
"esk": {
"command": "esk-mcp",
"args": []
}
}
}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, Zod schemas, .env.example |
The MCP binary is feature-gated behind mcp to keep the main CLI binary lean.
cargo xtask sandbox builds a release binary and scaffolds a test project in /private/tmp/esk-test with mock CLI shims and sample secrets.
cargo xtask sandbox
cargo xtask sandbox --cleanRelease from Cargo.toml version in one command:
cargo release-tagThis 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:
cargo xtask release --dry-run