Secrets Management for Developers: How to Handle API Keys and Passwords Properly
API keys, database passwords, and JWT secrets require specific handling at every stage. Here's the complete guide to managing secrets without leaking them.
Secrets management has one non-negotiable rule: secrets never go into git. Everything else is tradeoffs between convenience, cost, and security posture appropriate for your team's size and risk profile. This guide covers what not to do, what to do instead at each stage (local development, CI/CD, and production), and how to handle the situation when a secret has already leaked.
What Not to Do
Never commit .env files with real values. This is the most common mistake. An .env file committed to a git repository - even a private one - creates permanent risk. Git history persists. Former employees retain access to cloned repositories. Repository access controls change.
Never hardcode secrets in source code.const API_KEY = "sk-live-..." in a TypeScript file is the same as committing it to git. Even if you "remember to remove it before pushing," you won't always remember.
Never share secrets over Slack, email, or chat. Chat logs persist. They're searchable. They often sync to cloud services with broader access than your secrets store.
Never use the same secret across environments. Production database credentials should be different from staging credentials. Development API keys should be different from production API keys. When a secret leaks, you want to rotate only the affected environment, not all environments.
Local Development: .env.local in .gitignore
The standard pattern for local development is a .env.local file that is explicitly ignored by git.
In GitHub Actions, store secrets in the repository or organization secrets store (Settings > Secrets and variables > Actions) and reference them in workflows:
Never exposed in logs (GitHub redacts them if they appear)
Scoped to the repository or organization
Not accessible to pull requests from forks (protecting against supply chain attacks)
For organization-wide secrets shared across multiple repositories, use organization-level secrets in GitHub.
Production: A Secrets Manager
For production deployments, environment variables set in a hosting platform (Vercel, Railway, Coolify, Fly.io) are the minimum viable solution. They store secrets encrypted and inject them at runtime.
For teams that want a dedicated secrets manager:
Doppler is the practical choice for small teams. It provides a UI for managing secrets across environments (dev, staging, production), syncs secrets to your hosting platform, has a CLI for local development (doppler run -- pnpm dev), and audits who accessed what and when. Pricing: free for early-stage teams (up to 5 users and 5 projects), $6/user/month after that.
AWS Secrets Manager is the choice for teams already on AWS infrastructure. It stores secrets encrypted, rotates them automatically, and integrates with IAM for fine-grained access control. Cost: $0.40/secret/month plus API call costs.
1Password Secrets Automation integrates with 1Password, which many teams already use as their password manager. Secrets are available via CLI, SDKs, or direct injection into CI/CD environments. Cost: $19.95/month for teams.
HashiCorp Vault is the self-hosted option for teams that cannot use cloud secrets managers for compliance reasons. Complex to operate, powerful, free to self-host.
The Accidental Commit Problem
If a secret is committed to git, treat the secret as compromised immediately, even if the repository is private.
Step 1: Rotate the secret immediately. Generate a new API key, change the database password, regenerate the JWT secret. Do this before anything else. The old secret is no longer safe.
Step 2: Remove the secret from git history.git rm removes the file from the next commit but not from history. To purge from history, use git filter-repo (the modern replacement for BFG):
All collaborators will need to re-clone the repository after a force push. The history rewrite propagates to the remote but any existing clones retain the old history.
Step 3: Audit access. Check your secret provider's audit logs for any activity with the compromised key between when it was committed and when you rotated it.
Detecting Secrets Before They're Committed
gitleaks scans git history and staged changes for secrets:
brew install gitleaks
gitleaks detect --source .
Integrate as a pre-commit hook to catch secrets before they're committed:
# .git/hooks/pre-commit
gitleaks protect --staged
GitHub secret scanning automatically scans repositories for known secret patterns (AWS keys, Stripe keys, GitHub tokens) and alerts you if it finds them. This is enabled by default on public repositories and available for private repositories on GitHub Advanced Security.
Secret Rotation Policy
Secrets should be rotated regularly, not just when they leak. A practical rotation schedule:
Production database passwords: every 90 days
JWT secrets: every 90 days (requires all existing sessions to re-authenticate)
Third-party API keys: every 180 days, or immediately after any team member leaves
CI/CD secrets: when the team member who set them up leaves
Doppler and AWS Secrets Manager support automated rotation for some secret types (database passwords via RDS, for example).
Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace - chat, projects, time tracking, AI meeting summaries, and invoicing - in one tool. Try it free.
Practical deep-dives on LLMs, developer tools, and AI engineering. No filler. Unsubscribe any time.
// written byFIG. AUTH-01
530
Mahmudul Haque Qudrati
CEO & ML Engineer
CEO and ML Engineer at Pristren. Builds AI-powered software for teams and writes about machine learning, LLMs, developer tools, and practical AI applications.
Open Code Review – An AI-powered code review CLI tool: A Practical Overview
Open Code Review is an open-source CLI tool from Alibaba that uses AI to review code changes. It runs locally, supports multiple LLMs, and costs about $0.01 per review. Here's a practical breakdown.