Why a .env file is not a safe place for secrets
A .env file is a plaintext list of KEY=value lines that a loader reads into your process at startup. It is the fastest way to get a secret into an app, and it is where most secret leaks start. The same failure modes show up in every language:
- It ends up in git history after a stray
git add .or a missing.gitignoreline. - It gets baked into Docker images and build artifacts and travels with them.
- It gets serialized into crash dumps, CI logs, and error trackers.
- It cannot rotate without editing and redeploying the file on every machine that holds a copy.
- It has no audit and no scope: any process on the box can read every value, and you cannot tell what read which secret.
If you are looking for an alternative to .env files, the durable one is to stop keeping secrets in files at all. That is what SikkerKey does, and the reason .env files are popular, that they are dead simple, is the bar it is built to meet. You bootstrap a machine once, and your code reads secrets with no file, no token, and no config.
One command to set up, nothing to wire up
Getting a machine onto SikkerKey is a single command. You create a bootstrap token in the dashboard and run it on the server, container, or runner:
curl -sSL https://api.sikkerkey.com/v1/bootstrap/<token> | sh
That gives the machine its own identity on the host. From then on, the CLI and every SDK find that identity on their own. There is no token to store in an environment variable, no key path to configure, and nothing to pass in your code. You set the machine up once, and reads just work.
A drop-in CLI for any language
The CLI is the direct replacement for a .env loader. sikkerkey run injects the secrets a machine can read as environment variables and runs your command, with no key or token in the line because it already knows the machine:
# inject everything this machine can read, then run your app
sikkerkey run --all -- node app.js
# pick specific secrets, or scope to one project
sikkerkey run --secret sk_db_prod --secret sk_stripe_prod -- ./worker
sikkerkey run --all --project production -- node app.js
# preview what would be injected, without running anything
sikkerkey run --all --dry-run
Migrating is a one-line change to your start command: node --env-file=.env app.js becomes sikkerkey run --all -- node app.js, and your code keeps reading from the environment exactly as before. When you want a file or a single value instead, sikkerkey export --format dotenv writes one on demand, and sikkerkey get sk_api_key prints a single secret.
Six native SDKs that find the machine for you
If you would rather read secrets in code, SikkerKey ships a native SDK for Python, Node.js, Go, .NET, Kotlin/JVM, and PHP. Each one detects the machine's identity for you, so you create a client with no arguments and read a secret by id:
# pip install sikkerkey
from sikkerkey import SikkerKey
sk = SikkerKey() # finds this machine's identity for you
api_key = sk.get_secret("sk_stripe_prod")
The other five follow the same shape, and all are read-only with minimal or zero dependencies. Full reference is in the SDK docs.
One encrypted vault, with rotation built in
Your secrets live in one encrypted vault, organized into projects, instead of the same plaintext file copied onto every machine. Because they live in one place, the things a .env file cannot do come for free:
- Rotation on a schedule. Any secret can rotate on its own, and every machine picks up the new value on its next read. No editing files, no fleet-wide restart.
- Database credentials that rotate themselves. Point SikkerKey at a database role and it sets a new password on schedule and updates the database for you.
- Structured secrets. Keep related fields, like host, user, and password, in one secret and rotate them individually.
- Canaries. Plant a tripwire credential among the real ones; the moment a machine reads it, SikkerKey freezes the project and alerts you.
- One-time share links. Hand a credential to a person with a link that self-destructs on first view, instead of pasting it into chat.
- Versioning. Every change keeps the previous value for rollback, and a deleted secret is recoverable for 30 days.
Per-secret access and a full audit log
A .env file holds the secret in the clear, so anything on the box can read it, and you never find out who did. SikkerKey gives you the opposite by default. A machine reads only the secrets you granted it, and it proves who it is on every request without carrying a reusable token, so there is nothing in a file or a log for someone to copy. Every read is recorded against a specific machine, with the time and source, and streamed to your dashboard live, so a secret read at 3am from an unfamiliar host is something you hear about rather than piece together later.
Wherever your code runs
The same one-command setup covers every place a .env file ends up. Long-lived servers bootstrap as above; containers can take a ready-made identity from the dashboard; CI runners and autoscaled fleets enroll themselves per run with short-lived identities that expire on their own; and AI coding agents get their own identity that can manage the vault while staying locked out of secret values.
Replacing your .env file
A .env file is convenient because the secret is right there, which is exactly why it leaks. SikkerKey is the alternative that keeps the same simplicity, one command to set a machine up and sikkerkey run --all -- in front of your start command, while the secret stays in an encrypted vault and reaches a machine only when it asks. You get rotation, an audit log, and per-secret access on top, in place of a plaintext file any process can read.
Create your first secret and point one machine at it to see the whole flow end to end.