What is a .env File?

A .env file (pronounced "dot env") is a plain text configuration file that stores environment variables for your application. It follows a simple KEY=VALUE format and is loaded at application startup by libraries like dotenv (Node.js), python-decouple (Python), godotenv (Go), or vlucas/phpdotenv (PHP).

The core idea behind .env files comes from the Twelve-Factor App methodology, which recommends storing configuration in the environment rather than in code. This means the same codebase can run in development, staging, and production simply by swapping out the .env file — no code changes required.

Common things stored in .env files include database connection strings, API keys, third-party service credentials, feature flags, port numbers, and application mode settings.

.env File Format Rules

The .env format is simple but has important rules that differ slightly across parsers. Following these consistently avoids subtle bugs:

Basic KEY=VALUE syntax

# One variable per line
PORT=3000
NODE_ENV=production
APP_NAME="My Application"

Quoting values

Values containing spaces, special characters, or that need to preserve leading/trailing whitespace must be quoted. Both single and double quotes are supported by most parsers, but double quotes are more portable:

# Required: spaces in value
APP_DESCRIPTION="A production-ready web application"

# Required: special characters like # or $
DB_PASSWORD="p@$$w0rd#2026"

# Optional but harmless: simple values
PORT="3000"

Multiline values

For private keys or certificates that span multiple lines, use double-quoted values with literal newlines or the \n escape sequence (parser-dependent):

# Literal newlines inside double quotes (dotenv v15+ syntax)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

# Alternative: escaped newlines (more portable)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"

Comments

Lines starting with # are comments and are ignored by parsers. Inline comments (after a value on the same line) are not universally supported and should be avoided:

# This is a safe comment on its own line
PORT=3000

# AVOID: inline comments can break parsers
# PORT=3000 # web server port  <-- value becomes "3000 # web server port"

Naming Conventions for Environment Variables

Consistent naming conventions make your .env files easier to understand and audit. Follow these established patterns:

Use SCREAMING_SNAKE_CASE

Environment variable names should be all uppercase with words separated by underscores. This is the universally accepted convention across all operating systems and languages:

# Correct
DATABASE_URL=postgres://...
MAX_UPLOAD_SIZE_MB=50
STRIPE_SECRET_KEY=sk_live_...

# Wrong
# databaseUrl=postgres://...
# maxUploadSize=50
# stripe-secret-key=sk_live_...

Prefix by service or feature area

Group related variables with a common prefix. This makes the file scannable and avoids naming collisions when multiple services are configured:

# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_production
DB_USER=appuser
DB_PASSWORD=your_strong_password

# AWS
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG
AWS_REGION=us-east-1
AWS_S3_BUCKET=my-app-assets

# Stripe
STRIPE_PUBLIC_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Never Commit .env to Git

This is the most critical rule. A .env file committed to a public repository exposes your secrets to the entire internet — and git history is permanent. Even if you delete the file in a subsequent commit, the secrets remain accessible in the history.

Add .env to .gitignore first

Before you write a single line of code in a new project, add .env to your .gitignore. This is the first file you should create:

# .gitignore

# Environment files - NEVER commit these
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# But DO commit the example template
# !.env.example

What to do if .env was already committed

If a .env file has already been committed to your repository, take these steps immediately:

  1. Rotate all exposed credentials immediately. Every secret in that file must be considered compromised, regardless of whether the repo is public or private. Invalidate API keys, change passwords, rotate tokens.
  2. Remove the file from git tracking: git rm --cached .env
  3. Add .env to .gitignore and commit that change.
  4. Rewrite history to scrub the file. For small repositories, use BFG Repo Cleaner. For repos with sensitive history, consider using git filter-repo.
  5. Force-push the cleaned history: git push --force --all
  6. If the repository was public or has been forked or cloned, assume the secrets are permanently compromised even after history rewriting.

Create a .env.example Template

The .env.example file (also called .env.sample or .env.template) is the complement to your .gitignore entry. It documents every variable your application needs without exposing actual values. This file should be committed to your repository.

# .env.example
# Copy this file to .env and fill in your values
# Never commit .env — only commit this file

# Application
NODE_ENV=development
PORT=3000
APP_URL=http://localhost:3000

# Database — get credentials from your DBA or cloud console
DATABASE_URL=postgres://user:password@localhost:5432/mydb

# Stripe — find in dashboard.stripe.com/apikeys
STRIPE_PUBLIC_KEY=pk_test_your_key_here
STRIPE_SECRET_KEY=sk_test_your_key_here

# OpenAI — find at platform.openai.com/api-keys
OPENAI_API_KEY=sk-your_openai_key_here

Good practices for your .env.example:

  • Include every variable the application needs to run, with no exceptions.
  • Use placeholder values that indicate where to get the real value (e.g., sk_test_your_key_here rather than just an empty string).
  • Add comments explaining non-obvious variables and where to obtain credentials.
  • Keep it in sync with the actual .env file — update it whenever you add or remove variables.
  • Optionally include safe non-secret defaults (like PORT=3000) so new developers can get started quickly.

You can also use Docker Compose to load a shared .env file across services:

# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    env_file:
      - .env
    ports:
      - "${PORT}:${PORT}"

  worker:
    build: ./worker
    env_file:
      - .env          # shared variables
      - .env.worker   # worker-specific overrides

Secret Management in Production

Here is the uncomfortable truth: you should not use .env files in production. Files on disk are a poor way to manage secrets at scale. They can be accidentally logged, included in container images, read by compromised processes, or left behind on decommissioned servers.

For production workloads, use a dedicated secrets manager:

ToolBest ForKey Features
AWS Secrets ManagerAWS-hosted appsAutomatic rotation, fine-grained IAM, audit logs
AWS Parameter StoreAWS apps, simple configsFree tier, hierarchical naming, KMS encryption
HashiCorp VaultMulti-cloud, self-hostedDynamic secrets, PKI, database credential leasing
DopplerDeveloper teamsGit-like branching for configs, CLI sync, integrations
Google Secret ManagerGCP-hosted appsVersion control, IAM-based access, audit logging
Azure Key VaultAzure-hosted appsHSM-backed keys, certificate management, RBAC
InfisicalOpen-source teamsSelf-hostable, E2E encrypted, secret rotation

The pattern for production is to inject secrets as environment variables at container start time via your orchestrator (Kubernetes Secrets, ECS task definitions, Fly.io secrets), not by shipping .env files with your deployments.

Framework-Specific .env Loading

Most modern frameworks extend the basic .env format with their own conventions. Understanding these prevents "why is my variable undefined?" bugs:

Next.js

Next.js loads variables in this priority order: .env.local > .env.[environment] > .env. Only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. All others remain server-side only:

# Server-only (never sent to browser)
DATABASE_URL=postgres://...
OPENAI_API_KEY=sk-...

# Exposed to browser bundle (use with caution)
NEXT_PUBLIC_APP_URL=https://myapp.com
NEXT_PUBLIC_STRIPE_KEY=pk_live_...

Vite

Vite uses the VITE_ prefix to expose variables to client-side code. Unprefixed variables are only available in vite.config.js, not in your app code:

# Only accessible in vite.config.js
SOME_BUILD_CONFIG=value

# Accessible in client code via import.meta.env.VITE_API_URL
VITE_API_URL=https://api.myapp.com
VITE_APP_TITLE="My App"

Create React App (CRA)

CRA requires the REACT_APP_ prefix for all custom variables. Variables without this prefix are ignored:

# Available as process.env.REACT_APP_API_URL
REACT_APP_API_URL=https://api.myapp.com
REACT_APP_VERSION=1.0.0

# Ignored by CRA (not prefixed)
# SECRET_KEY=this-wont-be-bundled

Validate Your .env File

Even experienced developers make mistakes with .env files — a missing quote, an accidentally committed secret, or a variable that follows the wrong naming convention. Before pushing to staging or production, it pays to run a quick validation.

Our ENV Inspector tool checks your .env file entirely in your browser (zero server-side processing). It detects exposed secrets by key name pattern, flags format issues like unquoted values with spaces or invalid key names, and auto-generates a safe .env.example with secrets redacted.

Validate Your .env File Now

Paste your .env file to detect secrets, check format, and generate a safe .env.example. All processing is 100% in-browser — your secrets never leave your machine.

Open ENV Inspector →

Related Developer Tools