There's no single winner, and articles that pick one are answering the wrong question. Secret management is five separate problems, and the tools that solve them barely overlap:

  1. Stopping secrets from entering Git
  2. Getting secrets into CI without storing them
  3. Distributing production secrets with audit and rotation
  4. Committing configuration safely
  5. Keeping one developer's own keys organised on their laptop

A tool that's excellent at (3) is usually irritating at (5), which is why teams end up with two or three. This is a decision map by layer, with what each tool is actually for and where it stops.

We make one of the tools in layer 5. Treat that section accordingly; the rest is a map.


Minimum viable stack, by situation

You areUse
Solo, side projects.gitignore + gitleaks + a local vault. That's it
Small team, one product in prodAbove + GitHub Actions secrets + your cloud's secret manager
Team with staging and multiple servicesAbove + Doppler or Infisical for env sync
Regulated, needs dynamic credentialsVault or OpenBao + SSO + audit, plus local vaults for dev ergonomics
Kubernetes-heavyExternal Secrets Operator or Sealed Secrets + a cloud store

Do not skip layer 0. A pre-commit scanner costs five minutes and prevents the most common real incident. Teams routinely adopt Vault and still leak keys through git add ., because Vault was never protecting that path.


Layer 0 — Keep secrets out of Git

The cheapest, highest-return layer, and the one most often skipped in favour of something more impressive.

ToolRole
.gitignore + .env.exampleBaseline. Free, mandatory
gitleaksScan staged changes locally and in CI
trufflehogDeeper scanning; can verify whether a found key is live
pre-commitFramework for running hooks consistently across a team
GitHub secret scanning + push protectionBlocks known patterns at push time. Free for public repos
brew install gitleaks

# Block a commit before the secret exists
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
gitleaks protect --staged --redact --verbose || {
  echo "gitleaks: staged changes contain a probable secret. Aborted."
  exit 1
}
EOF
chmod +x .git/hooks/pre-commit

Share hooks across a team with core.hooksPath pointing at a committed directory, so it isn't per-developer opt-in. Guide: keep secrets out of Git.

trufflehog's verification is worth knowing about: it can attempt a call against the provider to check whether a discovered credential is still valid, which turns a list of hits into a prioritised list of live exposures.


Layer 1 — CI without stored secrets

PlatformMechanism
GitHub Actionssecrets.*, environment protection rules, OIDC to cloud providers
GitLab CIMasked and protected variables, OIDC
CircleCI / BuildkiteContexts and project-level secrets

Prefer OIDC over stored cloud keys wherever it's available. Instead of an AKIA… key in your CI secrets, the runner presents a short-lived identity token and assumes a role:

permissions:
  id-token: write        # required for OIDC
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/github-deploy
      aws-region: eu-west-1

There is no long-lived credential to leak, rotate, or forget. This is the single biggest improvement available to most teams' CI, and it removes work rather than adding it. (Configuring OIDC in AWS)

Note that masking is not secrecy: CI providers redact known secret values from logs, but a secret transformed — base64-encoded, embedded in JSON, split across lines — often slips through unredacted.


Layer 2 — Team and production secret managers

ToolStrengthCost
HashiCorp VaultDynamic secrets, PKI, policy engineHigh operational burden
OpenBaoCommunity fork after the licence changeSame burden; evaluate governance
DopplerBest-in-class CLI and env sync for teamsSaaS, per-seat
InfisicalOpen source, self-hostable Doppler alternativeSelf-host or SaaS
AWS / GCP / Azure secret storesNative IAM integrationCheap, cloud-locked
1PasswordHuman logins and machine credentials in onePer-seat

Vault's distinguishing feature is dynamic secrets — it generates a database credential on request, valid for minutes, then revokes it. Nothing durable exists to leak, which is categorically stronger than rotating a static secret well. If you need that, nothing else on this list substitutes.

Vault's cost is equally real. Unsealing, storage backend, HA, upgrades, policy authoring. Adopting it to fix .env sprawl means taking on a control plane to solve an ergonomics problem — you'll get the operational burden and keep the sprawl.

If you're in one cloud, that cloud's secret manager is usually the right default. An app reads a secret via its instance role with no stored credential, and it's inexpensive. The lock-in is real but rarely the binding constraint.

Deeper: local-first vs cloud secret managers · Doppler vs local .env management.


Layer 3 — Encrypted configuration in Git

For config that belongs in version control but must not be plaintext.

ToolStrength
SOPSEncrypts values in YAML/JSON/env, keys stay readable — so diffs work
ageModern, simple key layer. Far less friction than GPG
Sealed SecretsKubernetes-native: commit an encrypted Secret, the controller decrypts in-cluster
External Secrets OperatorSyncs from a cloud store into Kubernetes Secrets

SOPS's design choice is the good one: encrypting only values means a diff still shows which keys changed, so code review remains meaningful. Fully encrypting the file makes every change an opaque blob.

brew install sops age
age-keygen -o ~/.config/sops/age/keys.txt
sops --encrypt --age <public-key> secrets.yaml > secrets.enc.yaml

The catch, and it's the whole game: you've moved the problem to key management. Now you must protect the age or KMS key, and getting it onto developer machines and CI runners securely is the actual work. SOPS with a cloud KMS key is usually cleaner than SOPS with distributed personal keys, because the cloud IAM handles access. See open source secret managers compared.


Layer 4 — Frontend and build-time exposure

Not usually listed as a "secret management layer", and it should be: this is where the highest-severity leaks happen.

Build tools inline prefixed variables into JavaScript sent to browsersNEXT_PUBLIC_*, VITE_*, REACT_APP_*. The secret ends up on a CDN, cached, possibly archived. No storage tool prevents this.

# Add to CI: fail the build if a live key reached the bundle
rg -q 'sk_live_[A-Za-z0-9]+' .next/static && { echo "LIVE KEY IN BUNDLE"; exit 1; }

The fix is architectural: secrets are read only in server code, and the browser calls your backend rather than the third party directly. How to prevent leaking API keys in frontend apps.


Layer 5 — The developer laptop

The layer cloud managers structurally don't reach, and where a large share of real leaks originate.

ToolStrengthLimitation
Apple KeychainBuilt in, hardware-backedNo project model, no auto-lock, poor bulk UX
PassStoremacOS-native, local-first, free, MITmacOS 26+ only, no sharing, no recovery
1Password / Bitwarden desktopSync, sharing, cross-platformBuilt around login forms; use the CLI
passScriptable, plain files, Git historyGPG key management; leaks secret names

PassStore: AES-256-GCM vault encryption, Argon2id key derivation, Touch ID unlock, auto-lock, clipboard auto-clear, workspace grouping per repo, vault health audit for reused secrets, encrypted backup export. No cloud sync and no account server, so there's no backend for data to reach. Source: github.com/ilmakio/PassStore · Security

Being explicit about the trade: no account means no password recovery, and no sharing. If either matters, use a cloud manager for that class of secret.

Roundup: best macOS apps for API keys · Guide: API key manager.


The rule that ties the layers together

One owner per secret. Everything else holds a pointer, not a copy.

Secret classOwner
Production credentialsCloud secret manager / Vault. Never on a laptop
CI credentialsCI secret store, or OIDC with nothing stored
Shared team credentialsA manager with sharing and audit
Your own dev and test keysLocal vault
Non-secret config.env, committed as .env.example

If a value exists in three systems, at least one is stale, and you'll find out which at the worst possible time. Most "our secret management is a mess" situations are really duplication problems, not tooling problems.


What should I use if I'm just one developer?

.gitignore plus gitleaks plus a local vault. Genuinely nothing else.

You don't need Vault, Doppler, SOPS, or a Kubernetes operator. Those solve team distribution and governance — problems you don't have. Adopting them solo means real operational cost for no benefit, and the usual result is abandoning them and going back to .env files.

Spend the effort on the two things that actually matter at your scale: a pre-commit scanner so you can't commit a key, and test-mode credentials instead of production ones on your machine. Those two prevent nearly every incident a solo developer realistically faces.


What changed going into 2026?

Three shifts worth planning around:

Short-lived credentials became the default answer. OIDC in CI, aws sso login locally, GitHub App installation tokens. The strategic move is no longer "manage static secrets better" but "have fewer static secrets." A credential that expires in an hour needs almost no storage strategy.

Push-time blocking became normal. Detection after the fact is now the fallback rather than the primary control, and free secret scanning on public repos has made "we didn't know" much rarer.

The OpenBao fork matured. HashiCorp's licence change pushed some organisations toward the community fork. If you're choosing now, evaluate governance and release cadence rather than assuming either is a drop-in for the other indefinitely.

What hasn't changed: the most common leak is still a plaintext file in a project directory, committed by someone in a hurry.