Lightweight & distributed / self-hosted / v0.7.5
A lightweight, distributed secret manager.
Run it in your own cloud — AWS, GCP, a VPS, Kubernetes, anywhere that runs a container. It brings its own database and its own accounts, so there is no identity provider to wire up and nothing to reach out to.
docker pull ghcr.io/clement880101/secret-manager
A real session
$ docker run -d -p 8000:8000 -v secretmgr-data:/data \ ghcr.io/clement880101/secret-manager:latest No API tokens existed, so one was created for user 'admin'. smt_sGQr0f3dFBP39sUNBm2jpwZ6tnZyFms-dLPcIUp6SwE $ secretmgr register alice Choose a password: Registered and logged in as alice $ secretmgr create db-pw hunter2 Stored secret `db-pw`. $ secretmgr share db-pw bob Granted access to `db-pw` for bob. $ secretmgr list db-pw $ secretmgr get db-pw hunter2
Run it in your cloud
One stateless container on port 8000, configured entirely through environment variables. It does not terminate TLS — whatever you put in front of it already does, which is what lets it run anywhere.
What you need
A container runtime. That is the whole list. No database to provision, no identity provider, no certificate, no config file, no accounts created in advance. Then, depending on what you are doing:
| Anything you would miss | SECRET_ENCRYPTION_KEY, or values are stored unencrypted |
| Clients on other machines | BACKEND_URL, and TLS in front of it |
| Not publicly signup-able | ALLOW_REGISTRATION=false |
| More than one replica | DB_URL at Postgres, and a fixed BOOTSTRAP_TOKEN |
| A specific listen port | PORT — honoured on Cloud Run and similar |
Docker
That is the whole setup. It brings its own database and issues its own tokens. Published for amd64 and arm64.
docker run -d -p 8000:8000 \
-v secretmgr-data:/data \
ghcr.io/clement880101/secret-manager:latest
For anything real, add SECRET_ENCRYPTION_KEY to encrypt values at rest and BACKEND_URL for the address clients reach.
Compose
The API and a Postgres with a persistent volume, together.
git clone https://github.com/clement880101/secret-manager
cd secret-manager/deploy
cp .env.example .env # fill it in
docker compose up -d
Anywhere else
Fly.io, Render, Railway, Cloud Run, Azure Container Apps and Kubernetes all build from the same Dockerfile, and terminate TLS for you.
Pick a database
SQLite is bundled and is the default, written to the /data volume. Nothing external needed for a single instance.
Point DB_URL at Postgres for more than one replica — the driver ships in the image too. A database inside the app container would give each replica its own copy.
Scale past one replica
Login state lives in the database, not in process memory, so a login can start on one instance and finish on another.
Rate-limit counters are shared too, so the limit is the limit rather than the limit multiplied by however many instances are running.
Encrypt at rest
Set SECRET_ENCRYPTION_KEY and values are encrypted before they reach the database. Keep the key — losing it makes existing values unreadable.
docker run --rm \
ghcr.io/clement880101/secret-manager:latest \
python -c "from cryptography.fernet import Fernet;
print(Fernet.generate_key().decode())"
Then get the CLI
Once your server is up, this is how people talk to it. A self-contained executable under 10 MB — no Python, no dependencies. Set BACKEND_URL to your deployment: there is no default, so it can never send your secrets somewhere you did not choose.
Then make it runnable
Downloads arrive without the executable bit. Rename it, mark it executable, put it on your PATH:
chmod +x secretmgr-* && sudo mv secretmgr-* /usr/local/bin/secretmgr
Check it: secretmgr ping → API healthy.
On macOS, the first run
These builds are not code-signed, so Gatekeeper refuses them until you say otherwise. Clear the quarantine flag:
xattr -d com.apple.quarantine /usr/local/bin/secretmgr
Or open it once from Finder with right-click → Open.
Verify what you downloaded
Every release ships a SHA256SUMS file built alongside the binaries.
shasum -a 256 -c SHA256SUMS --ignore-missing
How it works
Sign yourself up
secretmgr register creates an account on that deployment. No invitation and no external account. Close sign-ups with ALLOW_REGISTRATION=false on anything facing the open internet.
Store a key and value
Secrets are scoped to you. Keys are unique per account, so a second create on the same key is refused rather than silently overwriting.
Share with a teammate
Grant read access by whatever name you issued their token under. They see it in their own list; only you can delete or re-share it.
Commands
| secretmgr register NAME | Create an account on this deployment and log in. |
| secretmgr login NAME | Log in with your password. |
| secretmgr login --token T | Log in with a token the server issued. |
| secretmgr passwd | Change your password. |
| secretmgr revoke TOKEN | Revoke a token, after losing a machine. |
| secretmgr token USER | Issue a token for someone else. |
| secretmgr whoami | Show who you are and how the deployment authenticates. |
| secretmgr logout | Remove the stored token from this machine. |
| secretmgr create KEY VALUE | Store a new secret you own. |
| secretmgr update KEY VALUE | Replace a value, keeping who it is shared with. Deleting and recreating would drop the shares. |
| secretmgr list | List everything visible to you — what you own, plus what others have shared with you. |
| secretmgr share KEY USER | Give a teammate read access. They must already have an account. |
| secretmgr delete KEY | Delete a secret you own. |
| secretmgr ping | Check that the backend is reachable. |
| secretmgr version | Print the CLI version. |
Before you trust it with anything
Before you put real secrets in it
- Set
SECRET_ENCRYPTION_KEYand back it up. Without it values are stored unencrypted; lose it and they are unreadable, permanently. - Put TLS in front. The service speaks plain HTTP by design so it fits any platform. The CLI warns when it is talking cleartext to a remote host.
- A stolen token still grants access to everything that account can read — one key at a time now, rather than in a single request, and every read is recorded.
- Authentication is rate limited; the rest of the API is not. Failed logins are blocked per username and per address, but a valid token can be replayed as fast as the service answers.
- The audit trail is per-account. You can see your own activity; there is no administrator view across all users, and events are pruned after 90 days by default.
- No account recovery. A forgotten password needs an administrator to issue a token.
- Released binaries are unsigned. Verify the published SHA-256 checksums.
Questions
Do I need a GitHub account, or any identity provider?
No. The service has its own accounts and issues its own tokens. Nothing is registered anywhere else, and it makes no outbound network calls.
Does it need a separate database?
Not to start. The container bundles SQLite and a volume is the whole setup. Point DB_URL at Postgres when you want more than one replica, because SQLite cannot back concurrent writers.
Can it run more than one replica behind a load balancer?
Yes. Replicas hold no state in process memory, so no session affinity is needed. Give them one Postgres and a shared BOOTSTRAP_TOKEN, then scale to any number.
How are secrets stored?
Encrypted at rest with Fernet (AES-128-CBC and HMAC-SHA256) under the key in SECRET_ENCRYPTION_KEY. Without that variable values are stored in plaintext, so set it before storing anything real.
How do I rotate the encryption key?
Put the old key in SECRET_ENCRYPTION_KEYS_RETIRED and the new one in SECRET_ENCRYPTION_KEY, then run rotate_keys. It re-encrypts everything and reports how many secrets are still on the old key; only drop the retired key once that reaches zero.
What does it cost?
Nothing. It is open source under Apache 2.0, and it runs on your own infrastructure, so there is no account, quota or vendor involved.
How is this different from Vault or AWS Secrets Manager?
It is deliberately much smaller. There is no identity provider to wire up, no policy language, no cloud account and no agent — a container and a CLI binary under 10 MB. That is the trade: far less to run and far less to learn, and far fewer features.