keep
Highlights
- Multi-recipient encryption (age/sops/PGP pattern) as a live service instead of a static file
- Server sees vault names, recipients, grants, and access timestamps — never plaintext or key material
- Read/write-scoped grants: a deploy identity can pull but can never rotate or grant others access
- Revoke is a metadata delete — no re-encryption; other recipients are untouched
- One-step rollback (--previous) for typo'd pushes, plus --purge for compromise-driven rotations that must not be recoverable
- Push/pull/grant are Ed25519-signed; admin actions (recipient management, revoke) use a separate password — every one of them logged, a real access log rather than just a git history of content changes
- keep overview: every vault and every grant on one screen, instead of checking each vault's access one at a time
- deploy.sh wrapper: pulls a project's env from keep, then runs docker compose up in its directory — the one-line replacement for a hand-copied .env on a VPS
- keep pull --template: a redacted, comment-preserving copy of the source .env is saved alongside the encrypted secrets on every push, so the 'why this exists' / 'get this from X' context an .env comment carries survives ingest instead of being silently dropped
- Verified end-to-end against a local server and a built Docker image, including two real bugs caught by that process
A small always-on service that holds encrypted secret bundles and lets authorized machines pull them, decrypted locally, without the server ever seeing plaintext.
The itch: a family of self-hosted projects, each with its own .env that exists today by being hand-copied onto whatever machine needs it. Rotating a credential means finding every machine with a copy. There’s no record of who has what. Onboarding a new deploy target means reconstructing a .env from memory or a password manager note.
keep isn’t Vault or Doppler — no policy engine, no dynamic secrets. It’s closer to age/sops: a symmetric key encrypts the payload, and that key is separately wrapped per authorized recipient’s public key. The difference from a committed encrypted file is the two things a static file can’t give you — live rotation (push once, everyone with a grant can pull the update) and a real access log (who actually fetched what, and when).
keep pull myapp/production fetches the payload plus this identity’s wrapped key, unwraps locally with its private key, and prints a decrypted .env-shaped result. Revoking a recipient is deleting their one wrapped-key row. Rotating is pushing again — cheap, since the recipient list rarely changes.
A plain key/value store throws away the comments a hand-maintained .env usually has — where a value comes from, when to rotate it, why it’s set the way it is. keep push now saves a redacted version of the source file (same structure and comments, values stripped) right alongside the encrypted secrets, retrievable with keep pull --template, so that context isn’t lost the moment a secret gets ingested.