Environment Variables: Security & Best Practices 2025
How to store config and secrets safely, what not to commit, and how to use env vars in local and production.
Environment variables are the foundation of secure application configuration. They separate sensitive data from your codebase, enable environment-specific settings, and are essential for modern DevOps practices. However, misusing environment variables can lead to security breaches, configuration errors, and deployment failures. Let's explore best practices for managing environment variables in 2025.
What Are Environment Variables?
Environment variables are key-value pairs that exist outside your application code. They're set in the operating system or runtime environment and accessed by your application at runtime. This allows you to:
- Store sensitive data (API keys, database passwords) separately from code
- Configure different settings for development, staging, and production
- Avoid hardcoding values that change between environments
- Follow the 12-Factor App methodology for configuration
Why Environment Variables Matter
Security
Hardcoding secrets in your source code is a critical security vulnerability. If your code is committed to version control, anyone with repository access can see your secrets. Environment variables keep secrets out of code.
❌ Never Do This:
const API_KEY = "sk_live_1234567890abcdef";
const DB_PASSWORD = "super_secret_password";
✅ Do This Instead:
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;
Environment-Specific Configuration
Different environments need different configurations. Development might use a local database, while production uses a managed service. Environment variables make this easy:
// Development
DATABASE_URL=postgresql://localhost:5432/myapp_dev
// Production
DATABASE_URL=postgresql://user:pass@prod-db.example.com:5432/myapp_prodWorking with .env Files
The most common way to manage environment variables in development is using .env files. These files are loaded by your application framework or a library like dotenv.
Basic .env File Structure
# Database Configuration
DATABASE_URL=postgresql://localhost:5432/myapp
DATABASE_POOL_SIZE=10
# API Keys
STRIPE_SECRET_KEY=sk_test_1234567890
GITHUB_TOKEN=ghp_abcdef123456
# Application Settings
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Feature Flags
ENABLE_NEW_FEATURE=true
ENABLE_BETA_FEATURES=falseEnvironment-Specific Files
Use different files for different environments:
.env- Default values (committed to git).env.local- Local overrides (gitignored).env.development- Development-specific.env.staging- Staging-specific.env.production- Production-specific (never commit)
Loading .env Files
// Node.js with dotenv
require('dotenv').config();
// Next.js (automatic)
// .env files are automatically loaded
// Python with python-dotenv
from dotenv import load_dotenv
load_dotenv()
// Ruby with dotenv
require 'dotenv/load'Security Best Practices
1. Never Commit Secrets
Always add .env files containing secrets to .gitignore. Create a .env.example file with placeholder values that can be committed:
# .gitignore
.env
.env.local
.env.*.local
# .env.example (committed to git)
DATABASE_URL=postgresql://localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_your_key_here
GITHUB_TOKEN=your_token_here2. Use Strong, Unique Secrets
Generate strong, unique secrets for each environment. Never reuse secrets between environments. Use a password manager or secret generator to create secure values.
3. Rotate Secrets Regularly
Regularly rotate API keys, database passwords, and other secrets. This limits the impact if a secret is compromised. Document your rotation process and schedule.
4. Use Secret Management Services
For production, use dedicated secret management services:
- AWS Secrets Manager - Integrated with AWS services
- HashiCorp Vault - Open-source secret management
- Azure Key Vault - Microsoft Azure's solution
- Google Secret Manager - Google Cloud Platform
- 1Password Secrets Automation - For teams
5. Validate Environment Variables
Always validate that required environment variables are present and have valid values. Fail fast if critical variables are missing:
// Node.js validation
const requiredEnvVars = [
'DATABASE_URL',
'STRIPE_SECRET_KEY',
'JWT_SECRET'
];
requiredEnvVars.forEach(varName => {
if (!process.env[varName]) {
throw new Error(`Missing required environment variable: ${varName}`);
}
});
// Using a library like envalid
import { cleanEnv, str, url } from 'envalid';
const env = cleanEnv(process.env, {
DATABASE_URL: url(),
STRIPE_SECRET_KEY: str(),
NODE_ENV: str({ choices: ['development', 'production', 'test'] })
});Naming Conventions
Use consistent naming conventions for environment variables:
- UPPERCASE: Use uppercase letters for all environment variables
- Underscores: Use underscores to separate words (SNAKE_CASE)
- Prefixes: Use prefixes to group related variables (e.g.,
DB_,API_) - Descriptive: Use clear, descriptive names
# Good naming
DATABASE_URL
STRIPE_SECRET_KEY
REDIS_HOST
REDIS_PORT
API_RATE_LIMIT
LOG_LEVEL
# Bad naming
db_url
stripeKey
redisHost
apiRateLimitType Safety and Validation
Environment variables are always strings. Convert and validate them appropriately:
// Type conversion
const PORT = parseInt(process.env.PORT || '3000', 10);
const ENABLE_FEATURE = process.env.ENABLE_FEATURE === 'true';
const MAX_CONNECTIONS = Number(process.env.MAX_CONNECTIONS) || 10;
// Validation with zod
import { z } from 'zod';
const envSchema = z.object({
PORT: z.string().transform(Number),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']),
ENABLE_FEATURE: z.string().transform(val => val === 'true')
});
const env = envSchema.parse(process.env);Production Deployment
Platform-Specific Configuration
Different platforms have different ways to set environment variables:
- Vercel: Set in dashboard or
vercel.json - Netlify: Set in dashboard or
netlify.toml - Heroku: Use
heroku config:set KEY=value - AWS Lambda: Set in function configuration
- Docker: Use
--env-fileordocker-compose.yml - Kubernetes: Use ConfigMaps and Secrets
CI/CD Integration
Set environment variables in your CI/CD pipeline for automated deployments:
# GitHub Actions
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
# GitLab CI
variables:
DATABASE_URL: $DATABASE_URL
STRIPE_SECRET_KEY: $STRIPE_SECRET_KEY
# CircleCI
environment:
DATABASE_URL: ${DATABASE_URL}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}Common Mistakes to Avoid
1. Committing .env Files
Always check your .gitignore before committing. If you accidentally commit secrets, rotate them immediately.
2. Using Default Values for Secrets
Don't use default values for secrets. If a secret is missing, the application should fail to start, not use a default value.
3. Logging Environment Variables
Never log environment variables, especially secrets. They can appear in logs, error tracking services, or be exposed in error messages.
4. Sharing Secrets in Chat/Email
Never share secrets via chat, email, or other insecure channels. Use secure secret sharing tools or secret management services.
5. Using Environment Variables for Everything
Don't use environment variables for values that rarely change or are part of application logic. Use configuration files or constants for non-sensitive configuration.
Environment Variable Tools
Useful tools for managing environment variables:
- dotenv - Load .env files in Node.js
- envalid - Validate and sanitize environment variables
- zod - TypeScript-first schema validation
- dotenv-cli - Run commands with .env files
- env-cmd - Execute commands with environment variables
Conclusion
Environment variables are essential for secure, flexible application configuration. By following best practices—never committing secrets, validating variables, using proper naming conventions, and leveraging secret management services—you can build secure applications that are easy to deploy across different environments.
Remember: security is not optional. A single leaked API key can compromise your entire system. Treat environment variables with the same care you treat your source code, and always err on the side of caution when handling secrets.
Try these tools
Use these tools alongside this guide
Part of the ThenCatch blog. Learn more about us or browse more guides.