Blog

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

  1. Committing .env to 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.

  2. Missing .gitignore entry from the first commit. If you add .env to .gitignore after already committing it, Git continues tracking the file. You need to explicitly remove it from tracking with git rm --cached .env.

  3. Not providing a .env.example file. Without one, new team members don't know which environment variables the application needs, leading to copy-paste sharing of real .env files over Slack or email.

  4. Sharing .env files via Slack, email, or Jira. This creates copies of production secrets in systems with weak access controls and long retention periods.

  5. Using the same .env across environments. Production database credentials in a development environment means every developer laptop is a breach vector for production data.

  6. Not validating required variables at startup. The application crashes in production because someone forgot to set STRIPE_SECRET_KEY after a deploy. Validate early, fail loud.

  7. Storing non-secret configuration alongside secrets. Mixing NODE_ENV=production with DATABASE_PASSWORD=hunter2 obscures 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 production environment'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

  1. Environment variable injection: The container orchestrator injects env vars at startup. Kubernetes Secrets, ECS task definitions, and Docker Compose all support this natively.

  2. 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.

  3. 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();
  1. 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 install from 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

  1. 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.

  2. Invalidate all active sessions that may have been created using the compromised credential. Session tokens can bypass passwords and MFA.

  3. 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

  1. 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.

  2. Determine the exposure timeline. When was the secret committed? When was it pushed to a remote? Use git log --all --oneline -- path/to/file to pinpoint the exact commit.

  3. 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

  1. Generate new credentials from a clean, trusted device.
  2. Update all systems that used the compromised secret.
  3. Remove the secret from Git history (see the next section).
  4. Audit for related exposures. If the secret was in a .env file, every other secret in that file should be considered compromised.

Recovery and post-incident

  1. Monitor for 30-90 days with enhanced logging on all systems the compromised secret could access.
  2. Notify affected parties if user data may have been accessed. GDPR requires notification within 72 hours.
  3. Write an incident report. Document the timeline, impact, root cause, and remediation steps. This is not about blame. It is about preventing recurrence.
  4. Update your prevention stack. Add pre-commit hooks, enable push protection, and review .gitignore coverage.

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

  1. 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.

  2. 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.

  3. 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.

  4. 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

  • .gitignore covering all secret file patterns from the first commit
  • .env.example in version control
  • dotenvx or manual .env management
  • 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:

Need help designing a secrets management strategy for your team? Let's talk.