TL;DR: Your API keys are passwords — treat them like it. Never put them directly in your code. Store them in .env files that are excluded from Git, use your hosting platform's secrets settings for production, and rotate any key that's ever been exposed. Bots scan GitHub constantly for leaked keys, and a single exposed AWS key can rack up tens of thousands of dollars overnight.

Why AI Coders Need This

Here's a pattern every vibe coder knows: You ask Claude or ChatGPT to build something that connects to Stripe, or OpenAI, or Supabase. The AI generates working code with placeholders like sk-your-api-key-here. You swap in your real key, the app works, and you move on to the next feature.

But where does that key live now? It's sitting right there in your source code. And if you git push that code to GitHub — even to a private repo that you later make public — that key is now on the internet. Forever.

Secrets management is the practice of keeping sensitive credentials out of your codebase and storing them safely. It's not complicated. It's not a "senior developer" topic. It's something you need from the very first time you use a real API key.

AI tools are incredibly good at building functional code. They are not good at thinking about what happens after you deploy. That's where you come in.

Real Horror Stories

These aren't hypothetical. These happen every single day.

💸 The $50,000 AWS Bill

A developer committed AWS access keys to a public GitHub repo. Within 4 minutes, automated bots found the keys and spun up cryptocurrency mining instances across every AWS region. By morning, the bill was over $50,000. AWS eventually helped reverse the charges, but the developer spent weeks on the phone with support. The bots that scan GitHub are automated, running 24/7, and they find keys faster than you can say "oops."

💳 Stripe Keys in Client-Side Code

A vibe coder asked AI to build a payment form. The AI put both the publishable key and the secret key in the React frontend. The publishable key is designed to be public — that's fine. But the secret key lets anyone issue refunds, view all customer data, and create charges. It was in the browser's source code for anyone to find with View Source.

🔓 The "Deleted" Commit

A developer realized they committed their .env file. They deleted it and pushed a new commit. Problem solved, right? No. Git keeps every version of every file forever. Anyone who clones the repo can see the deleted file in the commit history. The key was still exposed. You can't un-commit a secret — you have to revoke it and generate a new one.

GitHub's own security team reports that they detect millions of secrets leaked in public repos every year. In 2024, GitHub's secret scanning found over 39 million leaked secrets across public repositories. This isn't a niche problem — it's an epidemic.

What Counts as a Secret?

If someone could use this value to access your systems, charge your accounts, or read your data — it's a secret. Here's what you're probably handling:

Secret TypeExampleWhat Happens If Leaked
API Keyssk-proj-abc123... (OpenAI)Someone racks up charges on your account
Database Credentialspostgresql://user:pass@host/dbFull read/write access to all your data
JWT Secretsjwt-signing-secret-xyzAttackers forge authentication tokens, impersonate any user
OAuth Client SecretsGOOGLE_CLIENT_SECRET=abc...Attackers impersonate your app to OAuth providers
Webhook Signing Secretswhsec_abc123... (Stripe)Attackers send fake webhook events to your app
Encryption KeysENCRYPTION_KEY=aes256...All encrypted data can be decrypted
SMTP CredentialsSENDGRID_API_KEY=SG.abc...Spam sent from your email domain

Not secrets (safe to commit): PORT=3000, NODE_ENV=production, NEXT_PUBLIC_SITE_URL=https://myapp.com. These are configuration, not credentials. The test: would it cause damage if someone saw this value?

The .env File: Your First Line of Defense

A .env file is a simple text file that stores key-value pairs. Your app reads these values as environment variables at startup. Here's what it looks like:

# .env — NEVER commit this file
OPENAI_API_KEY=sk-proj-abc123def456ghi789
STRIPE_SECRET_KEY=sk_live_abc123def456
DATABASE_URL=postgresql://admin:s3cureP4ss@db.example.com:5432/myapp
JWT_SECRET=super-secret-jwt-signing-key-change-this
SENDGRID_API_KEY=SG.abc123.def456ghi789

Your code reads these variables instead of having keys hardcoded:

// ❌ NEVER do this
const openai = new OpenAI({ apiKey: "sk-proj-abc123def456ghi789" });

// ✅ Do this instead
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

For this to work, you need to do two critical things:

Step 1: Add .env to .gitignore

Your .gitignore file tells Git which files to ignore. Add .env so it never gets committed:

# .gitignore
.env
.env.local
.env.production
.env*.local

Do this BEFORE your first commit. If you add .env to .gitignore after you've already committed it, Git still tracks the old version. You'd need to run git rm --cached .env to untrack it, then commit that change. But the old commit with the key? Still in history. Rotate the key.

Step 2: Create a .env.example file

This file does get committed. It shows your team (or future you) what variables the app needs — without the actual values:

# .env.example — Commit this. Copy to .env and fill in real values.
OPENAI_API_KEY=your-openai-api-key-here
STRIPE_SECRET_KEY=your-stripe-secret-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
JWT_SECRET=generate-a-random-string-here
SENDGRID_API_KEY=your-sendgrid-api-key-here

When someone clones your repo, they copy .env.example to .env and fill in their own values. The code works, the secrets stay secret.

Platform-Specific Secrets: Where Your Production Keys Live

On your local machine, .env files work great. But when you deploy to production, you don't upload a .env file — you use your hosting platform's built-in secrets management. Every major platform has this:

PlatformWhere to Set SecretsHow It Works
VercelProject Settings → Environment VariablesSet per-environment (Development, Preview, Production). Automatically injected at build time and runtime.
RailwayService → Variables tabSupports shared variables across services. Auto-generates DATABASE_URL when you add a database.
NetlifySite Settings → Environment VariablesAvailable at build time. For serverless functions, use the same UI — they're injected automatically.
SupabaseProject Settings → APIProvides anon key (safe for client) and service role key (server-only). Never use the service role key in frontend code.
RenderService → Environment tabSupports environment groups for sharing secrets across services.
Fly.iofly secrets set KEY=valueCLI-based. Secrets are encrypted at rest and injected as env vars.

The pattern is the same everywhere: you set secrets in the platform's dashboard or CLI, and the platform injects them as environment variables when your app runs. Your code uses process.env.SECRET_NAME the same way it reads from a .env file — it doesn't know or care where the value came from.

What AI Gets Wrong About Secrets

⚠️ AI Failure #1: Hardcoding Secrets in Code

AI optimizes for "working code right now." When you ask it to connect to Stripe, it writes const stripe = new Stripe("sk_live_abc123") with a placeholder or — worse — with the key you pasted into the chat. Fix: Always tell AI: "Use environment variables for all API keys and secrets." Better yet, paste a .env.example file into your prompt so AI knows which variables exist.

⚠️ AI Failure #2: Committing .env to Git

AI-generated .gitignore files don't always include .env. If AI creates a project for you, check the .gitignore before your first commit. Fix: "Add .env, .env.local, and .env.production to .gitignore." Do this as step one of every new project.

⚠️ AI Failure #3: Using Secrets in Client-Side Code

This is the most dangerous one. AI sometimes puts secret keys in React components, Next.js client components, or browser-side JavaScript. Anything that runs in the browser is visible to anyone. Fix: Secret keys belong on the server only — in API routes, server actions, or backend services. In Next.js, only variables prefixed with NEXT_PUBLIC_ are meant for the browser. If you see a secret key used in a file with "use client", that's a critical security bug.

⚠️ AI Failure #4: Same Secrets in Dev and Production

AI doesn't distinguish between environments. It uses the same database URL, the same API key, the same everything. In development, you want test keys (Stripe test mode, a local database). In production, you want live keys. Fix: Maintain separate .env (local dev) and platform-level secrets (production). Use Stripe test keys (sk_test_) locally and live keys (sk_live_) in production.

⚠️ AI Failure #5: Never Rotating Keys

AI sets up a key once and never mentions rotation. Keys should be rotated regularly — and immediately if there's any chance of exposure. Fix: Set a reminder to rotate production keys every 90 days. If you've ever pasted a key into ChatGPT, Slack, or email — rotate it now. Those platforms are secure, but it's a habit that will save you from the time you paste into the wrong place.

Tools for Secrets Management

For most vibe-coded projects, .env files plus your platform's environment variables are enough. But as your project grows, here's what the next level looks like:

ToolWhat It DoesWhen You Need It
DopplerCentralized secrets dashboard. Syncs secrets to all your environments and platforms automatically.When you're managing secrets across multiple services (frontend, backend, workers) and tired of updating each one manually.
InfisicalOpen-source secrets management. Similar to Doppler but self-hostable. Has a CLI, SDKs, and integrations.When you want Doppler-like features but prefer open-source or need to self-host for compliance.
1Password Secrets AutomationInjects secrets from your 1Password vault into your app's environment. Great if you already use 1Password.When your team already uses 1Password and you want one place for both personal passwords and app secrets.
HashiCorp VaultEnterprise-grade secrets management with automatic rotation, audit logs, and fine-grained access control.When you're running production infrastructure, need compliance audit trails, or manage secrets for multiple teams.

When is .env enough? If you're a solo builder or small team with one or two deployed apps, .env files locally and platform environment variables in production are perfectly fine. You don't need Doppler or Vault. Upgrade when you're managing more than 10-15 secrets across multiple services, when you need audit logs, or when team members need different access levels to different secrets.

The Vibe Coder's Secrets Checklist

Every project. Every time. No exceptions:

✅ .env is in .gitignore BEFORE the first commit
✅ .env.example exists with placeholder values (committed to git)
✅ No API keys or passwords hardcoded anywhere in source files
✅ Secret keys are only used in server-side code (never in the browser)
✅ Different keys for development vs production
✅ Platform environment variables set for production deployment
✅ Keys rotated immediately if ever exposed
✅ ChatGPT/Claude conversations don't contain real API keys

That last one catches people off guard. When you paste your code into an AI chat to debug something, make sure you've redacted any real keys first. Replace them with sk_test_REDACTED or similar. AI doesn't need your real key to help you debug.

Quick Reference: The Complete .env Workflow

# 1. Create your project
npx create-next-app my-app
cd my-app

# 2. FIRST THING: Make sure .env is in .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.production" >> .gitignore

# 3. Create .env with your real secrets (local only)
cat > .env << 'EOF'
OPENAI_API_KEY=sk-proj-your-real-key
DATABASE_URL=postgresql://admin:password@localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_your-test-key
EOF

# 4. Create .env.example for others (committed to git)
cat > .env.example << 'EOF'
OPENAI_API_KEY=your-openai-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_your-stripe-test-key
EOF

# 5. Verify .env is ignored
git status
# .env should NOT appear in the list

# 6. Safe to commit
git add -A
git commit -m "Initial commit with .env.example"

Frequently Asked Questions

Revoke the key immediately — don't just delete the commit. Git history preserves everything, so even deleted files are accessible. Go to the service dashboard (Stripe, OpenAI, AWS, etc.), revoke the leaked key, and generate a new one. Then add .env to .gitignore to prevent it happening again. Assume the key was already scraped — bots find exposed keys within minutes.

For most vibe-coded projects, yes. A .env file kept out of version control with .gitignore works for local development, and your hosting platform's environment variables handle production. You'd upgrade to a dedicated secrets manager (like Doppler or Infisical) when you have a team, need audit logs, or manage secrets across many services.

All secrets are stored as environment variables, but not all environment variables are secrets. Your PORT=3000 or NODE_ENV=production aren't sensitive — anyone can see them. But your STRIPE_SECRET_KEY or DATABASE_URL with a password absolutely are. The test: would it cause damage if someone saw this value? If yes, it's a secret.

AI optimizes for working code, not secure code. Hardcoding a key makes the example self-contained and functional immediately. The AI isn't thinking about what happens when you push to GitHub — it's solving the immediate problem you asked about. That's why you need to recognize placeholder keys and move them to .env files before committing.

Immediately if they've ever been exposed (committed to git, shared in a screenshot, pasted in chat). Otherwise, every 90 days is a good baseline for production apps. For side projects, rotate when you have any suspicion of exposure. The more damage a key can cause (payment processing, cloud infrastructure), the more frequently you should rotate it.