Secrets Management for Developers: From .env Files to Production Vaults
23.8 million secrets leaked on GitHub in 2024, and 60% were exploited within hours. Here's how to manage secrets from local dev to production.
In 2024, 23.8 million secrets were leaked on public GitHub repositories. That's a 25% increase over the previous year, and GitHub's own scanning detected over 39 million leaked secrets across public and private repos combined. The rate works out to more than a dozen exposed secrets per minute, every minute, all year.
Here's the number that should keep you up at night: 60% of leaked credentials are exploited within 12 hours of exposure. But the average time to detect a leaked secret is 194 days. That gap between exposure and detection is where breaches happen. According to IBM's 2025 Cost of a Data Breach Report, breaches involving stolen credentials cost $4.67-$4.81 million on average, and breaches that take more than 200 days to detect cost $5.01 million.
The good news: most secrets leaks are entirely preventable. A combination of .gitignore rules, pre-commit hooks, and a proper secrets manager costs nothing and blocks the vast majority of accidental exposures. This guide covers the full stack of secrets management, from your local .env file to production vault infrastructure, with specific tools, configurations, and cost comparisons at every level.
If you're building with a security-first mindset, secrets management is one of the highest-leverage investments you can make.
The Scale of the Problem
Before diving into solutions, it's worth understanding exactly how bad the problem is, because the numbers inform every recommendation in this guide.
What's leaking and where
GitGuardian's State of Secrets Sprawl 2025 report paints a detailed picture of what developers are accidentally committing:
| Secret Type | Share of Public Repo Leaks | Notes |
|---|---|---|
| Generic secrets (hardcoded passwords, DB creds, custom tokens) | ~58% | The majority of all detected leaks |
| MongoDB credentials | ~21% | Most common specific type in public repos |
| AWS IAM keys | ~12% | 8% rate in private repos vs 1.5% in public |
| Google API keys | ~9% | |
| AI service tokens (OpenAI, Anthropic, Hugging Face) | Rapidly growing | 65% of Forbes AI 50 companies had confirmed leaks |
Two findings stand out. First, generic secrets, things like hardcoded passwords and database connection strings, account for more than half of all leaks. These are not exotic edge cases. They're the DATABASE_URL you pasted into a config file and forgot about. Second, private repositories are 8 times more likely to contain secrets than public ones. Developers behave more carelessly when they think nobody's looking, but private does not mean secure.
The problem extends beyond code repositories. GitGuardian found that 6.1% of Jira tickets and 2.4% of corporate Slack channels contained exposed credentials. Sharing secrets over chat and project management tools has become a significant vector.
The AI factor
Repositories using GitHub Copilot had a 6.4% secret leakage rate. AI-assisted code generation is accelerating development, but it's also accelerating the creation of code that embeds credentials. AI service tokens, keys for OpenAI, Anthropic, Hugging Face, and similar platforms, are now the fastest-growing category of leaked secrets.
Why leaked secrets persist
Perhaps the most alarming statistic: 70% of secrets leaked in 2022 remain active today. Teams detect the leak, maybe even remove the file from the latest commit, but they don't revoke the credential itself. Or they don't realize that Git history preserves every version of every file, so the secret is still one git log away from any attacker with repo access.
.env Files: Getting the Basics Right
The .env file is where most developers first encounter secrets management. It's also where most secrets leaks originate. Getting this right prevents the majority of accidental exposures.
The seven most common .env mistakes
-
Committing
.envto version control. This is the single most common cause of secrets leaks. Once a secret is in your Git history, it persists even after you delete the file, because Git stores every version of every file ever committed. -
Missing
.gitignoreentry from the first commit. If you add.envto.gitignoreafter already committing it, Git continues tracking the file. You need to explicitly remove it from tracking withgit rm --cached .env. -
Not providing a
.env.examplefile. Without one, new team members don't know which environment variables the application needs, leading to copy-paste sharing of real.envfiles over Slack or email. -
Sharing
.envfiles via Slack, email, or Jira. This creates copies of production secrets in systems with weak access controls and long retention periods. -
Using the same
.envacross environments. Production database credentials in a development environment means every developer laptop is a breach vector for production data. -
Not validating required variables at startup. The application crashes in production because someone forgot to set
STRIPE_SECRET_KEYafter a deploy. Validate early, fail loud. -
Storing non-secret configuration alongside secrets. Mixing
NODE_ENV=productionwithDATABASE_PASSWORD=hunter2obscures what actually needs protection.
The .gitignore pattern you need
Add this to your .gitignore before your first commit. Not after. Before.
# Environment files
.env
.env.local
.env.*.local
.env.development
.env.staging
.env.production
# But DO track the example/template
!.env.example
!.env.template
# Other secret files
*.pem
*.key
*.p12
*.pfx
credentials.json
service-account*.json
The .env.example pattern
Commit a .env.example file that documents every required variable without containing real values:
# .env.example -- committed to repo, NO real values
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_xxx
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
REDIS_URL=redis://localhost:6379
OPENAI_API_KEY=sk-xxx
Every new developer clones the repo, copies .env.example to .env, fills in their own values, and never needs to ask anyone to Slack them a file full of production secrets.
Validate at startup
Don't let missing variables become runtime errors in production. Validate them when the application starts:
// config.ts
const required = [
'DATABASE_URL',
'STRIPE_SECRET_KEY',
'SESSION_SECRET',
] as const;
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
This takes five minutes to implement and prevents an entire category of production incidents.
Secrets Management Tools: From Free to Enterprise
The .env file works for solo development, but it breaks down the moment you have a team, multiple environments, or production infrastructure. Here's the full landscape of tools, organized by complexity and cost.
Tool comparison
| Tool | Free? | Self-Host? | Setup Complexity | Best For |
|---|---|---|---|---|
| dotenvx | Yes (open source) | N/A (CLI) | Low | Encrypted .env files in version control |
| git-crypt | Yes (open source) | N/A (CLI) | Low | Simple file encryption in Git |
| SOPS | Yes (open source) | N/A (CLI) | Medium | GitOps, encrypted config files |
| Infisical | Yes (MIT core) | Yes | Low-Medium | Developer-friendly teams, growing orgs |
| Doppler | Free for 3 users | No (SaaS only) | Low | Small teams, fast setup |
| 1Password | $7.99/user/month | No | Low | Teams already using 1Password |
| HashiCorp Vault | BSL (free for internal use) | Yes | High | Enterprise, dynamic secrets |
| AWS Secrets Manager | $0.40/secret/month | No | Low (if on AWS) | AWS-native stacks |
| GCP Secret Manager | $0.06/version/month | No | Low (if on GCP) | GCP-native stacks |
| Azure Key Vault | $0.03/10K operations | No | Low (if on Azure) | Azure-native stacks |
Let me walk through each tier.
Tier 1: Encrypted files (free, no infrastructure)
dotenvx is from the creator of the original dotenv library. It encrypts .env files using ECIES (Elliptic Curve Integrated Encryption Scheme) with AES-256, so you can safely commit encrypted .env files to version control. Each environment gets its own encryption key, and the tool supports .env, .env.production, .env.staging, and any other variant.
# Install
npm install @dotenvx/dotenvx --save-dev
# Encrypt your .env file
npx dotenvx encrypt
# Run your app with decrypted env vars
npx dotenvx run -- node server.js
The philosophy is simple: encrypt the .env files themselves so they can live in your repo without risk. No external service, no infrastructure to manage.
SOPS (Secrets OPerationS) encrypts individual values within YAML, JSON, and INI files while keeping the file structure readable. This means you can diff encrypted config files in Git and see which keys changed, even though you can't see the values. SOPS integrates with AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, and the age encryption tool for key management.
# Encrypted with SOPS -- keys are readable, values are encrypted
database:
host: ENC[AES256_GCM,data:abc123...,type:str]
password: ENC[AES256_GCM,data:def456...,type:str]
api_keys:
stripe: ENC[AES256_GCM,data:ghi789...,type:str]
SOPS is the standard choice for GitOps workflows and works naturally with Kubernetes (via the Flux integration).
git-crypt encrypts entire files transparently using AES-256 in CTR mode. You configure which files to encrypt via .gitattributes, and git-crypt handles encryption on commit and decryption on checkout. It's the simplest option, but it has real limitations: no key revocation (you cannot remove a user's access once granted), no key rotation, and the smallest change requires storing the entire encrypted file. Use it for simple cases with small, trusted teams.
Tier 2: Managed platforms (low complexity, some cost)
Infisical is the open-source option in this tier. The core is MIT-licensed, self-hostable via Docker Compose, and includes SDKs for Node, Python, Go, Ruby, Java, and .NET. The cloud version has a generous free tier, with Pro starting at $9/month. Features include secret versioning, point-in-time recovery, rotation, dynamic secrets, and native syncs to GitHub, Vercel, AWS, and more. If I were starting a new team project today, Infisical would be my first recommendation for the price-to-feature ratio.
Doppler is a SaaS-only platform with a free tier for up to 3 users. It provides a centralized dashboard, native integrations with major platforms, environment management, audit logs, and versioning. The team tier starts at approximately $8/user/month. Doppler's strength is simplicity: the setup is fast and the DX is clean. The limitation is that you cannot self-host it.
1Password Secrets Automation is included with 1Password Business ($7.99/user/month). It provides a CLI tool for loading secrets into scripts, service accounts, a Connect server (private REST API), and pre-built integrations for GitHub Actions, CircleCI, and Jenkins. If your team already uses 1Password for password management, this is the path of least resistance.
Tier 3: Cloud provider vaults
If you're already on a cloud provider, their native secrets manager is often the simplest choice. Here's the cost comparison:
| Provider | Per-Secret Cost | API Calls | Free Tier |
|---|---|---|---|
| AWS Secrets Manager | $0.40/secret/month | $0.05/10K calls | $200 credit (new accounts after July 2025) |
| GCP Secret Manager | $0.06/active version/month | $0.03/10K calls | 6 versions, 10K ops, 3 rotations/month |
| Azure Key Vault | Free (secret storage) | $0.03/10K ops (Standard) | Per-operation billing only |
Azure Key Vault is the most cost-effective if you're looking at pure storage costs, since there's no per-secret charge. AWS Secrets Manager is the most expensive per secret but offers the deepest integration with AWS services (RDS, ECS, Lambda) and built-in rotation via Lambda functions. GCP Secret Manager sits in the middle with the most generous free tier.
Tier 4: HashiCorp Vault (enterprise-grade)
Vault is the industry standard for enterprise secrets management, but it comes with significant operational complexity. The open-source version (now under BSL 1.1, free for internal use) includes KV secrets engine, transit encryption, dynamic secrets, automatic secret rotation, audit logging, and policy-based access control. Enterprise features like namespaces, replication, and HSM auto-unseal require a commercial license.
Production Vault requires 4-32 GB RAM, 2-8 CPU cores, and an HA cluster. This is infrastructure you need to manage, patch, and monitor. For teams under 20 engineers, Vault is almost certainly overkill. Start with Infisical or Doppler and migrate to Vault when your organization's compliance requirements demand it.
Worth noting: after HashiCorp's relicensing to BSL, the community created OpenBao, a fork under MPL 2.0 (true open source). If the BSL license is a concern for your organization, OpenBao provides the same core functionality under a more permissive license.
Secrets Rotation: The Strategy Nobody Implements
Rotation is the most neglected aspect of secrets management. Teams generate a database password during initial setup and never change it. That password becomes a permanent liability.
What to rotate and how often
| Risk Level | Secret Type | Rotation Frequency |
|---|---|---|
| Critical | Production DB passwords, root API keys | Every 30 days |
| High | Cloud provider keys (AWS IAM), payment API keys | Every 30-90 days |
| Medium | Internal service tokens, monitoring API keys | Every 90-180 days |
| Low | Development/staging secrets, read-only keys | Every 180-365 days |
| On compromise | Any leaked or suspected-compromised secret | Immediately |
PCI DSS and similar compliance frameworks require regular rotation. But even outside compliance requirements, rotation limits the window of exposure if a secret is compromised without your knowledge.
Zero-downtime rotation: the dual-secret strategy
The fear of rotation is the fear of downtime. If you rotate a database password and your application still has the old one cached, you get a production outage. The dual-secret strategy eliminates this risk:
Phase 1 -- Normal Operation:
Secret A = CURRENT (active)
Secret B = (none)
Phase 2 -- Rotation Begins:
Secret A = CURRENT (still active)
Secret B = NEW (created, both valid simultaneously)
Phase 3 -- Transition:
All application instances refresh their secret cache
Grace period: both A and B work
Application picks up Secret B
Phase 4 -- Completion:
Secret A = REVOKED
Secret B = CURRENT (sole active credential)
The key implementation details:
- Grace period: Must be long enough for all application instances to refresh. If your secret cache TTL is 5 minutes, the grace period should be at least 10 minutes.
- No restart required: Use secret stores with in-process reload capabilities. The application fetches the new secret on its next cache refresh cycle without restarting.
- Alert on failure: Set up monitoring that fires if a secret is past its rotation window or if a rotation job fails.
AWS Secrets Manager automates this pattern natively for RDS, Redshift, and DocumentDB. For other services, you'll implement the rotation logic yourself in a Lambda function or cron job.
Automated rotation tools
- AWS Secrets Manager: Built-in rotation via Lambda for supported databases
- HashiCorp Vault: Dynamic secrets with TTL-based expiration (secrets are generated on demand and automatically revoked)
- Infisical: Built-in rotation for supported integrations
- Doppler: Configurable rotation intervals
- Custom automation: A scheduled job that implements the dual-secret strategy against your secrets manager's API
Vault's dynamic secrets approach is worth highlighting. Instead of rotating a long-lived credential, Vault generates a unique, short-lived credential for each application instance. When the TTL expires, the credential is automatically revoked. There's nothing to rotate because the secret was always temporary.
CI/CD Secrets: GitHub Actions, Docker, and Kubernetes
Your CI/CD pipeline is one of the most sensitive parts of your infrastructure. It has access to production credentials, deployment keys, and cloud provider accounts. A compromised pipeline is a compromised production environment.
GitHub Actions secrets
GitHub Actions provides three scopes for secrets:
| Scope | Use Case | Access Control |
|---|---|---|
| Organization | Shared across repos (e.g., NPM_TOKEN) | Can be scoped to specific repos |
| Repository | Project-specific (e.g., deploy keys) | Available to all workflows in the repo |
| Environment | Stage-specific (e.g., PROD_DB_URL) | Can require reviewer approval before access |
Limitations to know:
- 100 secrets per organization, 100 per repository
- 48 KB maximum size per secret
- Secrets are not available to workflows triggered by pull requests from forks
- Organization secrets are not accessible by private repos on GitHub Free
Best practices:
- Use environment secrets for production credentials with required reviewer approval. This means a human must approve before the
productionenvironment's secrets become available to a workflow run. - Scope organization secrets to specific repositories, not "all repos."
- Use deploy keys or service accounts instead of personal access tokens.
- Rotate critical CI/CD secrets every 30 days.
# Example: environment-scoped secrets with approval
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
API_KEY: ${{ secrets.PROD_API_KEY }}
run: ./deploy.sh
Docker build secrets
This is where many teams accidentally bake secrets into their container images. ARG and ENV values are stored in image layers and can be extracted by anyone with access to the image.
# WRONG -- secret persists in image layer, extractable by anyone
ARG DATABASE_URL
RUN echo $DATABASE_URL > /app/.env
# RIGHT -- secret available only during this RUN instruction
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=db_url \
export DATABASE_URL=$(cat /run/secrets/db_url) && \
npm run migrate
Build with:
docker build --secret id=db_url,src=./db_url.txt .
The --mount=type=secret directive makes the secret available only during that specific RUN instruction. It never appears in any image layer.
For multi-stage builds, use the build stage for operations that need secrets and the production stage for the final image:
# Stage 1: Build (has access to secrets)
FROM node:20 AS builder
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
RUN npm run build
# Stage 2: Production (no secrets present)
FROM node:20-slim
COPY --from=builder /app/dist /app/dist
CMD ["node", "/app/dist/server.js"]
The production image contains only the build output. No secrets, no build tools, no source code.
Kubernetes secrets
Native Kubernetes Secrets are base64-encoded, not encrypted. Anyone with API access can decode them. For production workloads, you need something stronger.
| Solution | Cost | GitOps Compatible | External Vault | Complexity |
|---|---|---|---|---|
| K8s native Secrets | Free | No (base64 only) | No | Low |
| Sealed Secrets (Bitnami) | Free | Yes | No | Low |
| External Secrets Operator | ~$530/month | Yes | Yes | Medium |
| Secrets Store CSI Driver | Free | No | Yes | Medium-High |
| Vault Agent Injector | Vault license | Yes | Yes (is Vault) | High |
Sealed Secrets is the most cost-effective option. You encrypt secrets with a public key, store the encrypted version in Git, and only the cluster can decrypt them with the private key. It is GitOps-friendly, free, and simple. The limitation is that it does not integrate with external vaults and has no centralized rotation.
External Secrets Operator (ESO) syncs secrets from external providers (AWS Secrets Manager, Vault, GCP Secret Manager, Azure Key Vault) into Kubernetes Secrets. This is the right choice for cloud-native teams that want centralized secrets management. The infrastructure cost is approximately $530/month.
Secrets Store CSI Driver mounts secrets as volumes directly from external providers. Secrets never touch etcd. This is the strongest option for compliance-sensitive workloads.
Runtime vs Build-Time Injection
This is the single most impactful architectural decision for secrets security: always prefer runtime injection over build-time secrets.
| Aspect | Build-Time | Runtime |
|---|---|---|
| When injected | During docker build / CI pipeline |
At container/app startup |
| Persistence | Baked into artifacts/image layers | Ephemeral, fetched on demand |
| Rotation | Requires rebuild and redeploy | Restart or live-reload |
| Security risk | Secrets in image history, CI logs | Minimal exposure window |
Runtime injection patterns
-
Environment variable injection: The container orchestrator injects env vars at startup. Kubernetes Secrets, ECS task definitions, and Docker Compose all support this natively.
-
Init container pattern: An init container fetches secrets from Vault or Secrets Manager and writes them to a shared volume. The application container reads from the volume on startup.
-
SDK fetch at startup: The application code calls the Secrets Manager API during initialization:
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function loadSecrets() {
const response = await client.send(
new GetSecretValueCommand({ SecretId: 'myapp/production' })
);
const secrets = JSON.parse(response.SecretString);
process.env.DATABASE_URL = secrets.DATABASE_URL;
process.env.STRIPE_KEY = secrets.STRIPE_KEY;
}
// Call before starting the application
await loadSecrets();
- Dynamic secrets: Vault generates a unique, short-lived credential for each application instance. When the TTL expires, the credential is automatically revoked. No rotation needed because the secret was always temporary.
When build-time secrets are unavoidable
Some operations genuinely need secrets during the build phase:
- Private package registry authentication (
npm installfrom a private registry) - Private Git repository access
- License keys for build tools
In these cases, use BuildKit's --mount=type=secret so secrets never appear in image layers. And keep build credentials completely separate from runtime credentials.
When a Secret Leaks: Incident Response Playbook
Despite all prevention measures, secrets leak. When they do, the speed of your response determines the damage. Here's the playbook.
Minutes 0-15: Immediate containment
-
Revoke the secret immediately. Do not investigate first. Do not assess impact first. Revoke. You can generate a new secret in minutes. You cannot undo unauthorized access that happened while you were deciding what to do.
-
Invalidate all active sessions that may have been created using the compromised credential. Session tokens can bypass passwords and MFA.
-
Assess scope. What does this secret grant access to? What data is reachable? Is there lateral movement risk to other systems?
Minutes 15-60: Impact assessment
-
Check access logs. Review CloudTrail, application logs, and API usage for unauthorized access since the secret was first exposed. Remember that the exposure may have started when the secret was committed, not when you noticed it.
-
Determine the exposure timeline. When was the secret committed? When was it pushed to a remote? Use
git log --all --oneline -- path/to/fileto pinpoint the exact commit. -
Identify lateral movement. Could the compromised secret have been used to access other secrets? If an AWS key was leaked, check if it had permissions to read from Secrets Manager.
Hours 1-4: Eradication
- Generate new credentials from a clean, trusted device.
- Update all systems that used the compromised secret.
- Remove the secret from Git history (see the next section).
- Audit for related exposures. If the secret was in a
.envfile, every other secret in that file should be considered compromised.
Recovery and post-incident
- Monitor for 30-90 days with enhanced logging on all systems the compromised secret could access.
- Notify affected parties if user data may have been accessed. GDPR requires notification within 72 hours.
- Write an incident report. Document the timeline, impact, root cause, and remediation steps. This is not about blame. It is about preventing recurrence.
- Update your prevention stack. Add pre-commit hooks, enable push protection, and review
.gitignorecoverage.
Secret Scanning and Cleaning Git History
Prevention: the layered approach
Prevention is cheaper than cleanup by orders of magnitude. Build these layers:
Layer 1: .gitignore -- Exclude .env, *.key, credentials.json
Layer 2: Pre-commit hooks -- TruffleHog, detect-secrets, gitleaks
Layer 3: Push protection -- GitHub native (enabled by default on public repos)
Layer 4: CI scanning -- GitGuardian, Snyk, Semgrep, Trivy
Layer 5: Periodic audits -- Scheduled scans of entire repository history
For pre-commit hooks, TruffleHog is the strongest option. It detects 800+ secret types with 700+ verified detectors, and it actively verifies whether detected credentials are live, which eliminates most false positives:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/trufflesecurity/trufflehog
rev: main
hooks:
- id: trufflehog
name: TruffleHog
entry: trufflehog git file://. --since-commit HEAD --only-verified --fail
This is also part of the free security toolstack that every team should have in place before writing their first feature.
Cleaning leaked secrets from Git history
Revoking the secret is step one. Cleaning it from history is step two, primarily to prevent re-exposure through forks, clones, or cached views.
BFG Repo Cleaner is 10-720x faster than git-filter-branch and handles the common case well:
# 1. Clone a fresh mirror
git clone --mirror git@github.com:user/repo.git
# 2. Create a file listing secrets to remove
echo "AKIAIOSFODNN7EXAMPLE" > secrets.txt
echo "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" >> secrets.txt
# 3. Run BFG
java -jar bfg.jar --replace-text secrets.txt repo.git
# 4. Clean up and force push
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push
git filter-repo is recommended by GitHub's official documentation and offers more flexibility:
# Remove a specific file from all history
git filter-repo --invert-paths --path .env
# Replace specific text across all history
git filter-repo --replace-text expressions.txt
# expressions.txt format:
# literal:AKIAIOSFODNN7EXAMPLE==>***REMOVED***
Critical warnings about history cleaning
-
Revoke the secret FIRST. Cleaning history does not undo the exposure. The secret was already public. Anyone who cloned or forked the repo already has it.
-
Force push is required. History rewriting changes all commit SHAs. Every collaborator must re-clone or rebase from the new history. A merge from an old branch will reintroduce the entire tainted history.
-
GitHub caches persist. Even after force-pushing, cached pull request diffs and views may still show the secret. Contact GitHub Support to purge cached views.
-
Forks retain old history. Any fork created before the cleanup still contains the secret. You cannot fix this without the fork owner's cooperation.
Cost Comparison: What Secrets Management Actually Costs
Here's a realistic cost comparison for a team of 10 engineers managing 50 secrets across 3 environments (development, staging, production):
| Solution | Monthly Cost | Annual Cost | Notes |
|---|---|---|---|
| dotenvx + git-crypt | $0 | $0 | Free, but manual, no audit trail |
| Infisical Cloud (Pro) | $9 | $108 | Self-host for $0 |
| Doppler (Team) | ~$80 | ~$960 | $8/user/month for 10 users |
| 1Password Business | ~$80 | ~$960 | $7.99/user/month for 10 users |
| AWS Secrets Manager | ~$20 | ~$240 | $0.40/secret x 50 secrets |
| GCP Secret Manager | ~$9 | ~$108 | $0.06/version, most cost-effective cloud option |
| Azure Key Vault | ~$1 | ~$12 | Per-operation only, cheapest cloud option |
| HashiCorp Vault (self-hosted) | $0 + infra | Infra costs | Requires dedicated ops time |
| HashiCorp Vault (HCP) | $0.03/hr+ | $260+ | Managed, starts at Standard tier |
For most teams, the sweet spot is either Infisical (self-hosted or cloud) for a dedicated secrets platform, or your cloud provider's native secrets manager if you're already committed to that ecosystem. Vault makes sense at enterprise scale when you need dynamic secrets, advanced policies, and multi-cloud support.
Putting It All Together: The Secrets Management Maturity Model
Level 1: Solo developer or early startup
.gitignorecovering all secret file patterns from the first commit.env.examplein version control- dotenvx or manual
.envmanagement - Pre-commit hooks with TruffleHog
- Startup validation for required environment variables
Level 2: Small team (2-10 engineers)
- Everything from Level 1
- Infisical or Doppler for centralized secret management
- GitHub Actions environment secrets with approval gates for production
- Docker BuildKit secrets for container builds
- Secret rotation schedule defined and tracked
Level 3: Growing team (10-50 engineers)
- Everything from Level 2
- Cloud provider secrets manager (AWS SM, GCP SM, Azure KV)
- Automated rotation with the dual-secret strategy
- External Secrets Operator or Sealed Secrets for Kubernetes
- Runtime injection only; no build-time secrets
- GitGuardian or equivalent for organization-wide scanning
Level 4: Enterprise
- Everything from Level 3
- HashiCorp Vault or equivalent for dynamic secrets
- Just-in-time credential generation
- Centralized audit logging across all secret access
- Compliance-driven rotation policies (PCI DSS, SOC 2, HIPAA)
- Secrets Store CSI Driver for zero-etcd-storage in Kubernetes
You don't need to start at Level 4. You do need to start at Level 1 from day zero.
Frequently Asked Questions
What's the fastest way to check if I've already leaked secrets in my repo?
Install TruffleHog and run trufflehog git file://. --only-verified against your repository. It scans the entire Git history, not just the current HEAD, and verifies whether detected credentials are still active. For a quicker check, run git log --all --diff-filter=A -- '*.env' '*.key' '*.pem' 'credentials.json' to see if secret files were ever committed.
Should I use my cloud provider's secrets manager or a third-party tool like Infisical? If your infrastructure is entirely on one cloud provider, their native secrets manager is the path of least resistance: tight IAM integration, no additional infrastructure, and low cost. If you're multi-cloud, hybrid, or want a better developer experience, Infisical or Doppler provides a unified interface across providers. The worst option is no secrets manager at all.
How do I handle secrets for local development without sharing production credentials?
Use environment-specific secrets. Production credentials should never exist on developer machines. Create separate development-only API keys, local database credentials, and test-mode payment keys. The .env.example pattern combined with per-environment configurations in Infisical or Doppler makes this straightforward.
Is it safe to commit encrypted .env files to version control? Yes, when using tools like dotenvx or SOPS that provide strong encryption (AES-256). The encryption must be per-value or per-file with proper key management. The risk is key management: if the decryption key is also in the repo or shared insecurely, the encryption is meaningless. Store decryption keys in a separate secrets manager or distribute them through a secure channel.
What should I do if a developer accidentally commits a secret? Revoke the secret immediately. Do not wait. Generate a new credential, update all systems using it, then clean the Git history with BFG Repo Cleaner or git filter-repo. Enable pre-commit hooks and push protection to prevent recurrence. If the secret was pushed to a public repo, assume it was compromised within minutes and treat it as a full incident.
How do secrets management practices affect SOC 2 and other compliance audits? SOC 2 Trust Service Criteria CC6.1 through CC6.8 cover logical and physical access controls, which directly include secrets management. Auditors look for evidence of secret rotation policies, access controls on credentials, audit logging of secret access, and incident response procedures for leaked credentials. Having a secrets manager with built-in audit logging, like Vault, Infisical, or AWS Secrets Manager, provides the evidence trail auditors need.
Keep reading:
- Security-First Development: A Practical Guide
- CI/CD Security Pipeline: How to Automate Security Checks
- Data Anonymization Patterns Every Developer Should Implement
Need help designing a secrets management strategy for your team? Let's talk.