What Are Environment Variables?
Hardcoding an API key into source code is one of those mistakes that seems minor until it isn't — until the repo goes public, or a contractor gets access, or a screenshot ends up in a Slack channel. Environment variables exist precisely to keep secrets out of code, and the pattern is simple enough that there's really no excuse not to use it.
The concept comes from the Unix operating system, where programs could access a set of variables through the process environment. Every modern operating system and deployment platform supports them, from your local laptop to AWS Lambda, Kubernetes, and Vercel.
Why Use Environment Variables?
The core reason to use environment variables is the separation of code and configuration. The Twelve-Factor App methodology — a widely-adopted guide for building software-as-a-service apps — states that configuration should be stored in the environment, not in the code.
- Security: API keys and passwords never appear in your source code or version history
- Flexibility: The same codebase can connect to different databases in development, staging, and production
- Portability: Anyone can clone and run your project by setting their own environment variables
- Auditability: You can change configuration without modifying or deploying code
The .env File Pattern
The most common pattern for local development is the .env file. This is a plain text file in your project root that contains key-value pairs. Libraries like dotenv (Node.js) or python-dotenv load these values into the process environment when your application starts.
# .env — never commit this file! DATABASE_URL=postgresql://user:password@localhost:5432/mydb STRIPE_SECRET_KEY=sk_test_abc123... JWT_SECRET=my-super-secret-key-change-in-production PORT=3000 NODE_ENV=development ENABLE_FEATURE_X=true
Add .env to your .gitignore immediately. Thousands of developers have accidentally leaked API keys, database passwords, and private keys by committing .env files. Once pushed to a public repository, credentials are compromised — even if you delete the file later, the history remains. Always add .env to .gitignore before your first commit.
The .env.example Pattern
Commit a .env.example file with all the required variable names but no real values. New team members copy this file, fill in their own values, and are immediately productive. This solves the documentation problem without ever committing secrets.
# .env.example — safe to commit, shows required variables DATABASE_URL=postgresql://user:password@localhost:5432/dbname STRIPE_SECRET_KEY=sk_test_your_key_here JWT_SECRET=generate-a-random-string-here PORT=3000 NODE_ENV=development
Reading Environment Variables in Code
Node.js
// Install dotenv: npm install dotenv require('dotenv').config(); // Load .env file const port = process.env.PORT || 3000; const dbUrl = process.env.DATABASE_URL; if (!dbUrl) { throw new Error('DATABASE_URL environment variable is required'); }
Python
# Install python-dotenv: pip install python-dotenv from dotenv import load_dotenv import os load_dotenv() # Load .env file db_url = os.getenv('DATABASE_URL') port = int(os.getenv('PORT', '3000')) if not db_url: raise ValueError('DATABASE_URL is required')
Docker and Docker Compose
# docker-compose.yml — reference .env file services: app: build: . env_file: - .env environment: - NODE_ENV=production - PORT=3000
Naming Conventions
Environment variables traditionally use SCREAMING_SNAKE_CASE (all uppercase with underscores). This convention distinguishes them visually from regular variables in code. Some established conventions:
- DATABASE_URL: Full connection string to the database
- PORT: The port the server should listen on
- NODE_ENV / APP_ENV: The current environment (development, staging, production)
- *_SECRET_KEY or *_API_KEY: Sensitive credentials with clear naming
- NEXT_PUBLIC_* or VITE_*: Framework prefixes that expose variables to the frontend
Frontend vs Backend Variables
Be very careful about which environment variables are exposed to the browser. Any variable accessible in client-side JavaScript is visible to every user who visits your site — even if you think it's hidden.
Frontend frameworks use naming conventions to distinguish safe public values from secrets. In Next.js, only variables prefixed with NEXT_PUBLIC_ are bundled into the client. In Vite, only VITE_ prefixed variables are exposed. All other variables stay server-side only.
If your Stripe secret key, OpenAI API key, or database password appears in your frontend JavaScript bundle, anyone can find it and use your services at your expense. Always call external APIs from your backend server, not directly from the browser.
Production Secrets Management
For production environments, .env files on servers are often insufficient. Dedicated secrets management tools provide better security, rotation, and audit trails:
- AWS Secrets Manager: Store and automatically rotate secrets, integrated with IAM
- HashiCorp Vault: Open-source secrets management for complex multi-cloud setups
- Doppler: Developer-friendly secrets manager that syncs across environments
- Vercel / Netlify Environment Variables: Built-in UI for managing variables in deployment platforms
- GitHub Secrets: Encrypted secrets for GitHub Actions workflows
Validation at Startup
A crucial best practice is validating all required environment variables when your application starts, before accepting any requests. Failing fast with a clear error is much better than discovering a missing variable when a user tries to make a payment at 2 AM.
// Validate required env vars at startup const required = [ 'DATABASE_URL', 'JWT_SECRET', 'STRIPE_SECRET_KEY' ]; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { console.error(`Missing required env vars: ${missing.join(', ')}`); process.exit(1); }
Conclusion
Environment variables are a foundational tool for building secure, portable, and maintainable applications. The key rules: never hardcode secrets in your code, always add .env to .gitignore, commit .env.example to document what's needed, and validate required variables at startup. As your application scales, consider moving to a proper secrets management system rather than relying solely on .env files.