Skip to main content
DevBench
All articles
devtoolssecurityjavascript

.env Files: Best Practices for Secrets, CI/CD, and Security

June 27, 20265 min read

The .env file pattern is ubiquitous in modern development — it is how applications separate configuration from code. It is also one of the most common ways secrets get accidentally leaked. The rules are simple but they are violated constantly.

.env file syntax

Each line is a key-value pair in KEY=value format:

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# API keys
STRIPE_SECRET_KEY=sk_live_...
OPENAI_API_KEY=sk-...

# App config
PORT=3000
NODE_ENV=production

# Multiline value (most parsers support quoted newlines)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEow...
-----END RSA PRIVATE KEY-----"

Lines starting with # are comments. Values do not need quotes unless they contain spaces or special characters. Most parsers (dotenv, python-dotenv) strip surrounding whitespace from values.

Rule 1 — never commit .env to Git

Add .env to your .gitignore immediately. One committed .env file containing production secrets can expose your entire infrastructure — even after you delete it, because secrets remain in git history.

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

If you have already committed a secret, rotating the credential is not enough — rewrite git history with git filter-repo or BFG Repo Cleaner, and assume the secret is compromised.

Rule 2 — commit .env.example

Every project should have a .env.example (or .env.template) file committed to the repo. It lists all the required environment variables with placeholder values or descriptions — no real secrets:

# .env.example — commit this, not .env
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_live_<your_stripe_key>
OPENAI_API_KEY=sk-<your_openai_key>
PORT=3000
NODE_ENV=development

New team members copy this file, fill in their own values, and are immediately productive. They also know exactly which secrets to obtain, without having to reverse-engineer the codebase.

Rule 3 — use different files per environment

  • .env — local development defaults (never secrets you cannot afford to lose)
  • .env.local — local overrides, not committed (per-developer values)
  • .env.test — test environment settings
  • .env.production — production values, never stored in the repo; injected by CI/CD

Next.js, Create React App, and Vite all follow this convention, loading files in a defined precedence order.

CI/CD: injecting secrets without .env files

In production and CI environments, use the platform's native secret management instead of .env files:

PlatformMechanism
GitHub ActionsRepository Secrets → accessed as ${{ secrets.MY_KEY }}
Vercel / NetlifyEnvironment variables dashboard → injected at build time
AWSSecrets Manager or Parameter Store → fetched at runtime
Docker--env-file flag or Docker Secrets (Swarm/Kubernetes)
KubernetesSecrets objects → mounted as env vars or volume files

Rule 4 — validate required variables at startup

Your application should fail fast with a clear error if a required environment variable is missing, rather than crashing later with a cryptic error:

// Node.js — validate at startup
const required = ["DATABASE_URL", "STRIPE_SECRET_KEY", "JWT_SECRET"];

for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
}

// Or use a validation library like zod
import { z } from "zod";

const env = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
  PORT: z.coerce.number().default(3000),
}).parse(process.env);

Common mistakes

  • Committing .env to a public repo — automated scanners (like GitHub's secret scanning) will find it immediately, but so will attackers
  • Logging environment variables — never console.log(process.env) in production; this prints all secrets to stdout/logs
  • Sharing .env files over Slack or email — use a password manager or a tool like 1Password Secrets Automation
  • Using the same secrets across environments — production and staging should have different credentials so a staging breach does not affect production
  • Storing secrets in .env and also in code — the .env pattern only works if the code truly has no hardcoded fallbacks

Try it yourself

Use the free browser-based .env Parser on DevBench — no signup, runs entirely in your browser.

Open .env Parser