Three tools dominate local secret management — HashiCorp Vault (dev mode), dotenv-vault, and SOPS — and picking the wrong one means either commit churn from encrypted blobs, a daemon nobody wants to run, or an onboarding step that silently breaks on a new laptop. This decision guide is part of local secret vaults and rotation within the environment sync, secrets and CI parity baseline, and it walks you from a side-by-side comparison to a concrete pick you can wire into onboarding and CI the same afternoon.

The three tools solve the same problem from opposite ends. Vault treats secrets as a live service you query at runtime; dotenv-vault treats them as an encrypted artifact you pull from a hosted service; SOPS treats them as ciphertext you version in the repo. Everything downstream — how a new hire bootstraps, how rotation propagates, how CI decrypts — follows from that one architectural choice. The rest of this guide makes that choice legible.

The decision in one table

Dimension HashiCorp Vault (dev) dotenv-vault SOPS
Setup cost High — run a server/container Low — npm package + login Low — single binary
Encryption model Server-held, in-transit Service-managed keys Client-side (age/KMS/PGP)
Git-friendliness Not committed (live store) Encrypted .env.vault committed Encrypted files committed
Rotation First-class (leases, TTL) Manual re-push Manual re-encrypt
Team scaling Strong (policies, identities) Good (managed access) Good (per-recipient keys)
CI integration Token/AppRole auth Service DOTENV_KEY Decrypt with KMS/age in CI
Offline use Needs running server Needs service for pull Fully offline
Best fit Mirroring prod secret infra Small teams wanting zero ops Git-native, infra-as-code shops

Read the table as a set of trade-offs rather than a scoreboard. No column wins every row, and the row that matters most for your team — usually rotation cadence, offline requirements, or how much operational surface you can afford — is the one that should drive the pick. The sections below unpack each tool so those rows become concrete.

Two rows deserve extra weight because they are hard to change later. The encryption model row determines where trust lives: Vault holds keys server-side, dotenv-vault delegates to a hosted service, and SOPS keeps decryption keys on the developer or runner. The git-friendliness row determines whether secret history is auditable in your repo or lives outside it. Getting these two right up front avoids a painful migration once a team has hundreds of secrets and months of history committed against one model.

How they actually differ

HashiCorp Vault (dev mode) runs a real secret server locally and most closely mirrors a production secret backend. Secrets live in the running store, never in git, and you get genuine leases and TTL-driven rotation. Reads happen over HTTP against http://127.0.0.1:8200, so an application fetches credentials at boot exactly the way it would in staging. The cost is operational: someone has to run the container, unseal or bootstrap the KV engine, and keep a token flowing to every process that needs a secret, as covered in the local secret vaults and rotation parent. Dev mode also runs unsealed and in-memory by default, so every restart wipes the store — a feature for disposable local work, a trap if someone expects persistence. Choose it when local must behave like staging.

dotenv-vault keeps an encrypted .env.vault file in the repo and decrypts it via a service-held DOTENV_KEY. Setup is the lightest of the three and onboarding is "log in and pull", which is genuinely appealing for a team that does not want to think about key management at all. The trade-off is a hard dependency on the dotenv service: if it is unreachable, a fresh clone cannot decrypt, and rotation means re-pushing the whole vault and redistributing the environment-scoped key. Access control is managed in the service's dashboard rather than in your repo, which is convenient until you need an audit trail that lives with the code. Good for small teams who want secret distribution without operating anything.

SOPS encrypts the values inside otherwise-readable YAML/JSON/ENV files using age, PGP, or a cloud KMS, and you commit the encrypted file. Keys stay readable while values become ciphertext, so a diff still shows which secret changed without leaking what it changed to. It is fully offline, git-native, and pairs naturally with infrastructure-as-code — the same .sops.yaml policy can govern local, staging, and production secret files. Rotation is manual re-encryption, and key distribution is your responsibility: every developer or CI runner needs an age key or KMS grant listed as a recipient. Choose it when you want secrets versioned alongside config with no running service.

Where each tool keeps the secret Three columns showing that Vault stores secrets in a live server, dotenv-vault in a hosted service, and SOPS in the git repository. Where the Secret Lives Vault (dev) live server, in memory read over HTTP :8200 leases and TTL nothing in git most like production dotenv-vault hosted service .env.vault in repo DOTENV_KEY decrypts pull to onboard lowest setup cost SOPS ciphertext in git age / KMS / PGP keys keys readable, values hidden fully offline git-native workflow
The storage location for each tool drives every downstream trade-off in onboarding, rotation, and CI.

Minimal setup for each

Each tool reaches a working local secret in a handful of commands. The blocks below are complete and runnable — start the store or install the binary, load one secret, then read it back to confirm the round trip works before wiring it into an application.

#!/usr/bin/env bash
# HashiCorp Vault dev mode
set -euo pipefail
docker run --rm -p 8200:8200 \
  -e VAULT_DEV_ROOT_TOKEN_ID=local-dev-token \
  hashicorp/vault:1.15

With the container running, point the CLI at it in a second shell and prove the KV round trip end to end:

#!/usr/bin/env bash
# Write and read one secret against the dev server
set -euo pipefail
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=local-dev-token
vault kv put secret/app DB_PASSWORD=s3cr3t
vault kv get -field=DB_PASSWORD secret/app
#!/usr/bin/env bash
# dotenv-vault: encrypt local .env and commit the vault file
set -euo pipefail
npx dotenv-vault@latest login
npx dotenv-vault@latest push
git add .env.vault && git commit -m "Update encrypted env"
# .sops.yaml — route which files SOPS encrypts and with which key
creation_rules:
  - path_regex: secrets/.*\.yaml$
    age: age1qxyz0examplekeyreplaceme
#!/usr/bin/env bash
# SOPS: encrypt in place, commit the ciphertext, decrypt on demand
set -euo pipefail
sops --encrypt --in-place secrets/local.yaml
git add secrets/local.yaml && git commit -m "Add encrypted local secrets"
sops --decrypt secrets/local.yaml > /tmp/local.env

The shape of onboarding falls out of these commands. Vault needs a running process plus a token in the environment; dotenv-vault needs a single login and a pull; SOPS needs the recipient key present on disk. The diagram below traces the same secret from creation to consumption for each tool so you can see which steps a teammate repeats on every new machine.

Secret flow from author to application A three-stage flow showing a secret authored, stored, then consumed by the application at boot. Author to Application Author secret put / push / encrypt Store server / service / repo Consume at boot read / pull / decrypt The middle stage is the only thing the three tools disagree about.
All three tools share the author-to-consume arc; only the storage stage differs.

Onboarding and rotation cost compared

The number that dominates day-to-day experience is not encryption strength — all three are cryptographically sound — but how many minutes a new machine needs before it holds a working secret, and how much effort a rotation costs. The chart below shows representative onboarding times for a first working secret on a clean laptop with the toolchain already installed. Vault is slowest because it involves starting a process and exporting a token; dotenv-vault is fastest because the flow is a single authenticated pull; SOPS sits in between, gated mostly by getting the recipient key onto the machine.

Onboarding minutes to first secret Bar chart comparing representative minutes to reach a first working secret for Vault, dotenv-vault, and SOPS. Minutes to First Working Secret Vault (dev) ~8 min SOPS ~5 min dotenv-vault ~3 min
Representative onboarding cost — the managed pull is fastest, the live server slowest.

Rotation flips some of that ranking. Vault rotates a lease or short-lived credential with no human in the loop once policies are set, so its recurring cost is the lowest of the three even though its setup cost is the highest. dotenv-vault and SOPS both require a human to re-push or re-encrypt and then get the change to every consumer, so their rotation cost is proportional to how often secrets change. If your credentials rotate weekly, Vault's operational overhead pays for itself; if they change twice a year, the manual tools are cheaper overall.

Decision guidance

  • Want local secrets to behave exactly like production, with real rotation and per-developer identities, and you can tolerate running a service: HashiCorp Vault.
  • Want the lowest-effort path for a small team and are comfortable depending on a managed service: dotenv-vault.
  • Want secrets versioned in git, offline-capable, and tied to your IaC workflow: SOPS.

A common hybrid: SOPS for committed, slow-changing config secrets and Vault dev for short-lived credentials that need rotation without restarting containers. The decision tree below routes the most common constraints to a single pick so you do not have to weigh every row of the table by hand.

Which tool to pick A decision tree routing offline needs to SOPS, minimal ops to dotenv-vault, and production parity to Vault. Pick a Tool Need runtime rotation and prod parity? Yes HashiCorp Vault Must work offline and live in git? Yes SOPS No — zero ops dotenv-vault
Route the two constraints that matter most — rotation parity and offline access — to a single pick.

CI integration for each

The choice also shapes how your pipeline reads secrets, and this is where teams most often discover a tool does not fit. Vault authenticates CI with a token or an AppRole and reads the same paths the application reads, so your pipeline exercises the real access path — the closest you get to a single consolidated parity check. dotenv-vault decrypts in CI with a per-environment DOTENV_KEY stored as a pipeline secret, which is one variable to manage but couples the build to the dotenv service being reachable. SOPS decrypts inside CI using an age key or a cloud KMS grant given to the runner, which keeps everything offline-capable but means the runner's identity must be listed as a recipient before any encrypted file will open.

#!/usr/bin/env bash
# CI decrypt patterns, one per tool
set -euo pipefail
# Vault: authenticate then read
VAULT_ADDR=https://vault.internal vault login -method=approle \
  role_id="$ROLE_ID" secret_id="$SECRET_ID" >/dev/null
vault kv get -field=DB_PASSWORD secret/app
# dotenv-vault: decrypt with the pipeline-held key
DOTENV_KEY="$DOTENV_KEY" npx dotenv-vault@latest decrypt > .env
# SOPS: runner key must be a recipient
SOPS_AGE_KEY="$AGE_KEY" sops --decrypt secrets/local.yaml > .env

Whichever tool you land on, keep the local and CI decrypt paths identical so a secret that opens on a laptop also opens in the pipeline. Diverging paths are the single most common cause of a green local run and a red build — a developer decrypts with a personal age key while the runner was never added as a recipient, or a DOTENV_KEY is set locally but missing from the pipeline. Add a single verification step that decrypts one known secret and fails loudly when it cannot, and run it identically in both places so the mismatch surfaces at setup time rather than deep in a build.

Apple Silicon (ARM64): Vault and SOPS publish arm64 builds; verify dotenv-vault's optional native deps install, or pin --platform linux/amd64 when running it inside a container. WSL2: keep age/PGP keyrings and .env.vault on the Linux filesystem so decryption hooks fire reliably and key permissions are honored. macOS (Docker Desktop): the Vault dev container binds to 127.0.0.1:8200 on the host; if another local service already owns 8200, remap with -p 8201:8200 and set VAULT_ADDR to match.

Frequently Asked Questions

Is HashiCorp Vault dev mode safe to use for anything beyond local work?

No. Dev mode runs unsealed, keeps everything in memory, and ships with a known root token you set yourself, so every restart wipes the store and the server has no persistence or seal protection. It is built for disposable local development and CI fixtures only. For anything shared or long-lived, run a properly initialized and sealed Vault instead.

Can I commit the SOPS-encrypted file to git safely?

Yes — that is the intended workflow. SOPS encrypts only the values while leaving keys readable, so the committed file shows which secret changed without exposing its plaintext. Anyone without a listed age key, PGP key, or KMS grant sees only ciphertext. The one rule is never to commit the decrypted output, so keep the plaintext file out of the repo with .gitignore.

What happens to dotenv-vault if the service is unreachable?

A fresh clone cannot decrypt .env.vault without contacting the service to resolve the DOTENV_KEY flow, so onboarding and CI both fail while the service is down. If offline capability is a hard requirement, choose SOPS, which decrypts entirely from a local key with no network call.

Can I use more than one of these tools together?

Yes, and many teams do. A common split is SOPS for slow-changing config secrets committed alongside infrastructure code, and Vault dev for short-lived credentials that rotate frequently. Keep each tool responsible for a clearly separated set of secrets so no value is defined in two places and drift stays impossible.