.env Files and Environment Variables Explained
Environment variables let you configure applications without hardcoding values into source code. .env files make them easy to manage locally. This guide explains how both work and how to use them correctly.
What Are Environment Variables?
Environment variables are key-value pairs available to a running process. Every process inherits a set of environment variables from its parent. Your shell has them:
# See all environment variables in your shell
env
# See a specific one
echo $HOME
echo $PATHWhen you run a Node.js, Python, or any other program, it inherits these variables. Your application can read them at runtime to configure its behavior without the values being hardcoded in the source.
Why Use Environment Variables?
The Twelve-Factor App methodology states: "Store config in the environment." The reasons:
- Security. Secrets (API keys, database passwords) don't end up in your git repository.
- Environment portability. The same code runs in development, staging, and production with different configuration.
- No code changes for config changes. Update an environment variable without redeploying code.
- Team isolation. Each developer uses their own credentials locally without conflicts.
What Is a .env File?
A .env file is a plain text file that lists environment variables, one per line:
DATABASE_URL=postgresql://localhost:5432/myapp
REDIS_URL=redis://localhost:6379
API_KEY=sk-abc123def456
JWT_SECRET=your-secret-key-here
PORT=3000
NODE_ENV=developmentThis file lives in the root of your project and is loaded at startup by a library (dotenv) or natively by your framework.
Using dotenv in Node.js
npm install dotenv// At the very top of your entry file (index.js, app.js)
require('dotenv').config();
// Or with ES modules
import 'dotenv/config';
// Now process.env has all your .env variables
const db = new Database(process.env.DATABASE_URL);
const port = process.env.PORT ?? 3000;In Node.js 20.6+, you can load .env without any library:
node --env-file=.env index.jsUsing Environment Variables in Other Languages
Python (python-dotenv):
pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
db_url = os.getenv('DATABASE_URL')
api_key = os.environ['API_KEY'] # raises if missingGo (godotenv):
import (
"github.com/joho/godotenv"
"os"
)
godotenv.Load()
dbURL := os.Getenv("DATABASE_URL")Shell / Docker:
# Load .env in bash
export $(cat .env | xargs)
# Docker Compose
services:
app:
env_file:
- .envThe .env.example File
Never commit .env to git. Do commit .env.example:
# .env.example — commit this
DATABASE_URL=
REDIS_URL=
API_KEY=
JWT_SECRET=
PORT=3000
NODE_ENV=development# .gitignore — add this
.env
.env.local
.env.*.local.env.example documents exactly which variables the app needs. New team members copy it to .env and fill in their own values:
cp .env.example .envVariable Naming Conventions
- Use
SCREAMING_SNAKE_CASE(all caps, underscores) - Prefix by service:
DATABASE_URL,REDIS_URL,STRIPE_SECRET_KEY - Boolean flags:
FEATURE_FLAG_NEW_UI=true - Framework-specific prefixes expose variables to the frontend:
NEXT_PUBLIC_(Next.js),VITE_(Vite),REACT_APP_(Create React App)
Validation at Startup
Fail fast if required variables are missing — don't let the app start silently misconfigured:
// Using zod for runtime validation
import { z } from 'zod';
const env = z.object({
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'test', 'production']),
}).parse(process.env);
export { env };Production: Beyond .env Files
.env files are great for local development but not ideal for production:
- Secrets managers: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault — store secrets encrypted, with access control and audit logs
- Platform env vars: Vercel, Railway, Heroku, Fly.io all have built-in environment variable management in their dashboards
- Doppler / Infisical: developer-friendly tools that sync secrets across environments and team members
The principle remains the same: configuration and secrets out of the codebase, injected at runtime from a trusted source.
Managing API secrets? Hash Generator for creating signing keys · Password Generator for strong secrets · JWT Decoder to inspect tokens
Frequently Asked Questions
Should I commit .env files to git?▾
Never commit .env files that contain real secrets (API keys, database passwords, private keys). Add .env to .gitignore immediately. Instead, commit a .env.example file with all the variable names but empty or fake values, so other developers know which variables they need to set. This documents the required configuration without exposing secrets.
What's the difference between .env, .env.local, .env.development, and .env.production?▾
.env is the base file, loaded in all environments. .env.local overrides .env for local development and should not be committed. .env.development and .env.production are loaded for their respective NODE_ENV values. The typical precedence (highest to lowest): .env.local > .env.[environment].local > .env.[environment] > .env. Frameworks like Next.js and Vite follow this pattern.
Are environment variables secure?▾
They're more secure than hardcoding secrets in source code, but they're not a complete security solution. Environment variables are accessible to all processes running as the same user, and they appear in /proc on Linux. For production secrets, prefer a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) over plain environment variables. Never log environment variables and avoid passing them through URLs.
How do I access environment variables in the browser?▾
You can't access server-side environment variables in the browser directly — they only exist on the server. Frameworks like Next.js (NEXT_PUBLIC_ prefix) and Vite (VITE_ prefix) selectively bundle specific environment variables into the client-side JavaScript at build time. Never expose secrets this way — anything bundled into the frontend is visible to anyone who inspects your JavaScript.