After this lesson, you will be able to: Keep API keys, database URLs, and tokens out of source code using environment variables and .env files, clean a leaked secret out of Git history, and manage secrets across environments and CI/CD.
A secret in your source code is a secret that will leak. This lesson covers environment variables, the .env file family, the .env.example pattern, .gitignore, what to do when a secret lands in Git history (it is compromised forever), platform secret dashboards, secrets in CI/CD, secret scanning tools, and key rotation.
An environment variable is a value the operating system hands to your process at startup, read in Node via process.env.MY_VAR. Secrets belong there, not in code, for one reason: code gets committed, shared, copied, and made public. The environment is per-deployment and never committed. The same code runs in dev, preview, and production with different secrets injected around it.
Never do the left. Always do the right. The key never appears in a file you commit.
// BEFORE (never do this): the key is now in Git history foreverconst stripe = new Stripe("sk_live_51H8xqL...realkey...");// AFTER: the key lives in the environment, not the repoconst key = process.env.STRIPE_SECRET_KEY;if (!key) throw new Error("STRIPE_SECRET_KEY is not set");const stripe = new Stripe(key);
.env holds defaults shared by everyone. .env.local holds your machine's real secrets and is never committed. .env.production holds production values (usually set in the platform dashboard instead of a file). .env.example is committed and contains every key with placeholder values, so a teammate cloning the repo knows exactly what to fill in. In Next.js, NEXT_PUBLIC_ prefixed vars are exposed to the browser; never put a secret behind that prefix.
Commit the example, ignore the real file. Add the ignore rule before your first commit, not after.
# .gitignore (add BEFORE your first commit).env.env.local.env*.local# .env.example (this one IS committed, with placeholders)DATABASE_URL=postgresql://user:password@localhost:5432/dbnameSTRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxNEXTAUTH_SECRET=generate-with-openssl-rand-hex-32
Rotate first, then scrub.
1. Rotate the leaked key in the provider's dashboard right now. The old value is dead.
2. Install git-filter-repo (pip install git-filter-repo).
3. Run: git filter-repo --replace-text <(echo 'sk_live_realkey==>REDACTED')
4. Force-push the rewritten history: git push --force.
5. Tell collaborators to re-clone; their old clones still contain the secret.
6. Confirm GitHub secret scanning no longer flags the repo.
Vercel, Railway, Render, and similar platforms let you set environment variables per environment (development, preview, production) in their dashboard. Set production secrets there, not in a committed file. In CI/CD, use GitHub Actions encrypted secrets (Settings > Secrets and variables > Actions) and reference them as ${{ secrets.MY_KEY }}. Never echo or console.log a secret in a pipeline; logs are often retained and visible to anyone with repo access.
GitHub secret scanning is on by default for public repos and alerts you (and often the provider) when a known key pattern is pushed. GitGuardian catches secrets before they are pushed via a pre-commit hook. truffleHog scans full history for entropy and known patterns. Rotate keys on a schedule even without a leak, and have a runbook for what to rotate the moment a key is exposed. Rotating without downtime usually means: issue the new key, deploy it, then revoke the old one once nothing uses it.
Pick the best first action.
Adding .env to .gitignore after the first commit (it is already tracked; you must also git rm --cached it). Putting a real secret behind NEXT_PUBLIC_ and shipping it to every browser. Committing .env.local because the .gitignore rule had a typo. Scrubbing history but forgetting to rotate, so the burned key is still live. Logging request headers (which include Authorization) at INFO level. Reusing the same secret across dev and prod, so a dev leak compromises production.
Sign in and purchase access to unlock this lesson.