Your .env file is in git. Your database password is in plain text. Your JWT secret is the same in dev and prod.
The Basics
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(["development", "production", "test"]),
});
export const env = envSchema.parse(process.env);
App crashes at startup if any env var is missing or wrong type. No more runtime surprises.
Secret Management in Production
Never store secrets in .env files in production. Options:
- Cloud provider secrets: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
- HashiCorp Vault: Self-hosted, dynamic secrets, auto-rotation
- Kubernetes secrets: Base64 encoded (not encrypted), use sealed-secrets or external-secrets operator
.gitignore Rules
.env
.env.local
.env.production
Commit .env.example with placeholder values. Never commit actual secrets.
Secret Rotation
Rotate secrets without downtime: support TWO valid secrets simultaneously during rotation. Accept both old and new JWT secrets during a transition window.
Part of my Production Backend Patterns series. Follow for more practical backend engineering.
If this was useful, consider:
- Sponsoring on GitHub to support more open-source tools
- Buying me a coffee on Ko-fi
You Might Also Like
- Environment Variables Done Right: From .env Files to Production Configs
- Production Secrets Management: From .env Files to HashiCorp Vault (2026 Guide)
- Docker Compose for Development: The Setup Every Backend Dev Needs
Follow me for more production-ready backend content!
If this helped you, buy me a coffee on Ko-fi!
Top comments (0)