I've reviewed hundreds of GitHub repos. The #1 security mistake? Hardcoded secrets. Here's how to handle environment variables properly in 2026.
The Problem
// DO NOT DO THIS
const db = mysql.connect({
host: 'production-db.example.com',
password: 'super_secret_password_123'
});
This code gets committed, pushed, and now your database password is public forever (yes, even if you delete the commit — git history remembers).
The Solution: .env Files + Environment Variables
Step 1: Create a .env file
DATABASE_URL=postgres://user:pass@host:5432/mydb
API_KEY=sk_live_abc123
JWT_SECRET=your-256-bit-secret
NODE_ENV=development
Step 2: Load it in your code
Node.js:
npm install dotenv
require('dotenv').config();
const db = mysql.connect({
host: process.env.DATABASE_HOST,
password: process.env.DATABASE_PASSWORD
});
Python:
pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
db_password = os.getenv('DATABASE_PASSWORD')
Step 3: NEVER commit .env
Add to .gitignore:
.env
.env.local
.env.production
Best Practices Checklist
1. Use .env.example (committed) + .env (gitignored)
# .env.example (committed — shows structure, no real values)
DATABASE_URL=postgres://user:password@localhost:5432/mydb
API_KEY=your-api-key-here
JWT_SECRET=generate-a-random-string
2. Validate env vars at startup
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
console.error(`Missing required env var: ${key}`);
process.exit(1);
}
}
3. Use different .env files per environment
.env.development
.env.staging
.env.production
4. Never log environment variables
// NEVER do this
console.log('Config:', process.env);
// If you must debug, redact:
console.log('DB connected to:', process.env.DATABASE_HOST);
5. Rotate secrets regularly
Set a calendar reminder. Rotate API keys and passwords at least every 90 days.
Production: Use a Secrets Manager
For production, .env files aren't enough. Use:
| Service | Free Tier | Best For |
|---|---|---|
| Doppler | 5 projects | Small teams |
| AWS Secrets Manager | 30-day trial | AWS deployments |
| Infisical | Unlimited (open source) | Self-hosted |
| Railway/Vercel env | Built-in | PaaS deployments |
Quick Audit: Is Your Repo Safe?
Run this in your repo:
# Check if .env is in .gitignore
grep -q ".env" .gitignore && echo "OK: .env is gitignored" || echo "WARNING: .env not in .gitignore"
# Check for hardcoded secrets (basic scan)
grep -rn "password\s*=\s*['\"]" --include="*.js" --include="*.py" --include="*.ts" .
grep -rn "api.key\s*=\s*['\"]" --include="*.js" --include="*.py" --include="*.ts" .
TL;DR
- Secrets go in
.env, never in code -
.envgoes in.gitignore, always -
.env.examplegets committed (with placeholder values) - Validate env vars at startup
- Use a secrets manager in production
More developer guides at lucasmdevdev.github.io
Top comments (1)
check out varlock.dev - it's a huge upgrade from default dotenv loading.