The short version
- What we built: a service that distributes secrets — .env files (files that bundle settings such as connection targets and passwords as environment variables), .npmrc (credentials for fetching packages), kubeconfig (connection information for server clusters), certificates, and so on — to each person's machine or to Continuous Integration (automation that continuously verifies changes, CI) with a single command:
dotvault pull. - Why we built it: API keys and
.envfiles get you a security violation if you put them on GitHub, and "the key is exposed, rotate it" if you hand them to an AI. You cannot distribute them, yet you have to distribute them. We built dotvault to resolve that contradiction at the root.
The article (about 9 min read)
This article is the first in a series that walks through why we built the secret-management service dotvault, what features it has, and how we developed it. Let me start with "why we built it."
You cannot distribute them, yet you have to distribute them
Development always requires sharing API keys and .env files (files that bundle settings such as connection targets and passwords as environment variables). The .env needed to start the app has to be shared among developers, and CI (automation that continuously verifies changes) won't run without it either.
But when you try to push these to GitHub (a code-sharing service), you're told it's a security violation — put them in .gitignore. When you try to hand them to an AI service like Claude Code, you're told the API key is exposed — rotate it (recreate the key). You cannot distribute them, yet you have to distribute them. I felt this contradiction was the real problem.
In the end you resort to distributing them through other services like Slack or Notion, which is very awkward. On top of that, when someone changes a .env, the change never lands in Git, so the other developers never hear about it. Development stalling because "an environment variable needed for startup was missing" or "a value had changed" was a constant occurrence. And this is not just our company. While consulting on development at various sites, I ran into the same problem again and again.
So: don't keep a .env file at all
That's why we changed our thinking at the root. The choice was: the project does not keep a .env file.
All secrets are registered in dotvault. Once a user logs in a single time via the npm CLI, they can then use dotvault pull and dotvault run. There is no need to hold a secret key per project.
Where this really pays off is that environment variables grow multiplicatively. The number of things to manage balloons as projects × environments (development, staging, production). With dotvault, no matter how many projects or environments there are, one login keeps the same secrets synchronized for everyone. That "synchronized for everyone" is what I wanted.
Let me start from what is running today
First, look at the finished form.
dotvault is a service for distributing secrets. It is running today as Software as a Service (business software used over the cloud, SaaS).
The admin console lists projects, service tokens (API keys for machines), members, and the audit log. Who touched which secret, and when — it's all recorded here.
We built it as a service rather than getting by with a short script, because for an organization, a procedure for distributing secrets is not enough. Who is allowed what. How much you hand to machines. Who touched things after you handed them over. How usage limits per plan are enforced. These four are managed as a system. External payment processing and automated billing are not part of this system.
Why does merely distributing secrets need this much of an admin screen? Because for organizational use, storage alone is not enough — you also need permissions and auditing. Let me first explain why we built it this way.
Why storage alone is not enough
Let's be clear about what is protected and where it is delivered. Secrets only become usable once they reach permitted people or execution environments, in only the scope that is needed.
One of the main reasons secrets leak is that there are too many places they live. Chat, email, each person's laptop, several servers. The more copies there are, the more paths there are to a leak. Rather than adding more procedures for hiding each copy, narrowing the storage down to one place is easier to manage.
The values in a .env are distributed to permitted people and servers, and used there.
dotvault doesn't just store secrets — it distributes them to permitted parties, in only the scope needed.
One place to manage storage, permissions, and auditing
So dotvault separates the storage of secrets from the code repository.
The server-side source of truth lives in dotvault, encrypted with KMS (Key Management Service). Because dotvault pull writes the fetched values into a local .env file, keeping that file out of Git's tracking is a separate operational step you still need.
On top of that, environments are separated: development, staging, and production — plus _shared, which every environment can reference. Even settings with the same name are different things in development and in production. We do not mix them.
And every single key carries an audit log. Who read which value, and when. Who changed it. Everything is recorded.
Fix a value in one place, and users get the new value on their next dotvault pull or at process startup. However, old .env files already fetched with dotvault pull are not deleted automatically, and values in already-running processes do not change until a restart. By concentrating the update source in one place and standardizing the re-fetch procedure, we reduce configuration drift between environments.
In the admin console you manage projects, service tokens, members, and the audit log. These are what you use to control where secrets are delivered and in what scope.
- Members assign permissions to people. This person, this project, up to this environment — that's the line you draw.
- Service tokens assign permissions to CI and servers. Machines, too, get only what they need.
- The audit log records who touched what after it was handed over. It's not "hand it over and done."
Storage location and permissions for secrets are managed here. That addresses the storage side of the problem.
Receiving takes one command
Next, the distribution side.
No matter how safely you store secrets, if receiving them is a hassle, people will go back to copying by hand. So receiving has to be finished in one command.
In dotvault, environments that need a .env file use dotvault pull. One command writes the secrets that person is permitted to have into the declared file on their machine or in CI.
What dotvault pull can fetch is only the permitted scope. If someone who holds only development permissions runs dotvault pull, production secrets do not come down. Because the fetched values are written to a local file, you still need configuration and practices to keep it out of the repository. For execution paths that should not create a file, use dotvault run -- <command>.
dotvault manages destinations, scope, and operation records as a system. Rather than not sharing secrets, we replaced sharing with a distribution path where you can trace who was given what.
Users don't need an AWS account. No difficult setup either. A person who joins simply pulls.
A newcomer, once granted permissions, can run dotvault pull themselves, cutting the time spent waiting for someone to send a .env. In CI that doesn't need a file, dotvault run -- <command> passes the key values into the child process's environment variables; we confirmed by implementation and tests that it does not create or modify a .env file. The key values do not persist in that process environment after the child process exits. On the other hand, automatically deleting files already created by dotvault pull, or automatically revoking already-issued secrets, are things we have not verified.
You can distribute not only to people, but to AI as well. Situations where you want an AI service to reference secrets during development are increasing, but pasting them into chat is exposure. dotvault has API and MCP (Model Context Protocol, a standard for AI tools to connect safely to external services) integrations, so an AI service can safely read and write secrets, and pull out an API key to access another service.
At design time we compared against Doppler and Infisical (existing secret-management services). We chose to build our own in order to match our organizational structure, our permission boundaries, and our per-plan usage limits. "Plans" here means usage caps and feature restrictions — not external payment processing like Stripe or automated billing.
We started writing from the spec at the end of May, and in the single month of June built up organizations, permissions, audit logging, and per-plan usage limits in 127 commits. Stripe integration, automated billing, and self-service billing were out of scope for this first version (v1). We decided the scope from the problem itself: "it's a secret, yet it has to be distributed."
The manual work we removed, and the judgment humans keep
Before dotvault, we would paste and send secrets, communicate the password through a separate channel, and confirm receipt. With dotvault pull, the needed values can be written out with one command after permissions are checked, so that manual work can be reduced.
Who is allowed which secrets is a human judgment. After the judgment, dotvault handles file generation, environment-variable injection into child processes, and operation records. The division of labor is: humans decide permissions and scope, and the routine distribution work is left to the service.




Top comments (0)