A secure environment variable management tool built using Rust. Self-host the server for your team and manage it with the CLI.
- Variable Creation: Securely store environment variables.
- Project and Environment Management: Categorize environment variables into projects and environments (e.g., development, staging, production).
- Secure Sharing: Share environment variables securely with team members through access controls.
No Rust toolchain needed once releases exist. For now, build from source:
make build-release # binaries in `target/release/`: `envoy`, `envoy-server`
./target/release/envoy --helpThe examples below assume envoy is on your PATH — substitute ./target/release/envoy if you built from source.
docker build -t envoy-server .
docker run -p 8080:8080 -v envoy-data:/data envoy-server
curl http://localhost:8080/healthData (SQLite + JWT secret) lives in the envoy-data volume (/data in the container). Don't skip the volume — without it redeploys wipe the DB and log everyone out.
Deploy however you like — bare binary, Docker, or any host that can run a container with a persistent volume. The server is stateless; all state is two files on disk:
| What | File | Configured by |
|---|---|---|
| Database (SQLite) | envoy.db |
TURSO_DATABASE_URL (a file path, or libsql://... for remote Turso) |
| JWT secret (auto-generated unless you set one) | .jwt_secret |
JWT_SECRET, else ENVOY_STATE_DIR/.jwt_secret |
How the secret directory is resolved: ENVOY_STATE_DIR if set, else the database file's parent directory, else ./data.
- Persist the state directory. Mount a persistent volume at whatever path you choose (the image defaults to
/data). Without this, redeploys wipe the DB and log everyone out. - Set
JWT_SECRETto a long random string via your host's secret management. If unset, the server generates one and stores it as.jwt_secretin the state directory — it survives restarts as long as the volume exists, but losing it invalidates all sessions. - Put TLS in front (reverse proxy / load balancer / your host's HTTPS). The server itself speaks plain HTTP — never expose it bare over the internet.
- Bind:
ADDRwins, else$PORT(for hosts that inject it), else0.0.0.0:8080. - Healthcheck: point your host at
/health(returns{"health":"ok"}; also wired to the DockerHEALTHCHECK).
Custom mount path example (any path works — /data is just the image default):
docker build -t envoy-server .
docker run -p 8080:8080 -v envoy-data:/mydata \
-e TURSO_DATABASE_URL=/mydata/envoy.db \
-e ENVOY_STATE_DIR=/mydata \
envoy-serverBare binary example:
TURSO_DATABASE_URL=/var/lib/envoy/envoy.db \
ENVOY_STATE_DIR=/var/lib/envoy \
JWT_SECRET=... \
ADDR=0.0.0.0:8080 \
./envoy-serverSee .env.example for all variables.
Point the CLI at it:
envoy manage connect --url https://<your-server>
envoy manage init # first user becomes instance admin (fresh server only)
envoy manage login- Persistent volume: required. Holds
envoy.dband.jwt_secret(defaults to/datain the image; override withTURSO_DATABASE_URL/ENVOY_STATE_DIR). Back it up. JWT_SECRET: set it via your host's secret management. Auto-generation is fine for trying out, but losing.jwt_secretinvalidates all sessions.- HTTPS: terminate in front of the server. Never expose it bare over the internet.
- Health:
/healthreturns{"health":"ok"}— wired to the DockerHEALTHCHECK; point your host's healthcheck at it too. - Logs:
ENVOY_LOG_LEVEL=info(default warn).
The examples below assume a release build — substitute target/debug/ if you ran make build.
Run the server (self-hosted — defaults to ./data/envoy.db, JWT secret auto-generated and persisted to ./data/.jwt_secret):
./target/release/envoy-serverWith explicit config:
JWT_SECRET=... TURSO_DATABASE_URL=./data/envoy.db ./target/release/envoy-serverRun the CLI:
./target/release/envoy --helpPoint the CLI at your server, then set it up:
envoy manage connect --url http://localhost:8080
envoy manage init # first user becomes instance admin (fresh server only)
envoy manage login
envoy manage invites create someone@example.com # admin creates a one-time invite
envoy manage accept --token <token> # invitee joins with their own password