Environment Variables Management - Complete Guide
Published: September 25, 2024 | Reading time: 15 minutes
Environment Variables Overview
Environment variables provide secure configuration management across different environments:
Environment Types
# Environment Categories
- Development (local development)
- Staging (pre-production testing)
- Production (live application)
- Testing (automated testing)
# Configuration Types
- Database connections
- API keys and secrets
- Feature flags
- Service endpoints
- Logging levels
Environment Variable Basics
Setting Environment Variables
Basic Commands
# Set environment variable (temporary)
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
# Set for current session
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" node app.js
# Set multiple variables
export NODE_ENV=production
export PORT=3000
export DEBUG=false
# Check environment variable
echo $DATABASE_URL
# List all environment variables
env
# Unset environment variable
unset DATABASE_URL
Environment Variable Naming
Naming Conventions
# Good naming conventions
DATABASE_URL # Database connection string
API_SECRET_KEY # API secret key
REDIS_HOST # Redis server host
LOG_LEVEL # Logging level
NODE_ENV # Node.js environment
# Avoid these patterns
db_url # Use uppercase
api-secret-key # Use underscores
myAppConfig # Use uppercase
123_CONFIG # Don't start with numbers
# Environment-specific prefixes
DEV_DATABASE_URL # Development database
STAGING_API_KEY # Staging API key
PROD_SECRET_TOKEN # Production secret
.env Files Management
Basic .env File
.env
# Application Configuration
NODE_ENV=development
PORT=3000
DEBUG=true
# Database Configuration
DATABASE_URL=postgresql://user:password@localhost:5432/myapp_dev
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=myapp_user
DB_PASSWORD=myapp_password
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# API Configuration
API_BASE_URL=https://api.example.com
API_KEY=your_api_key_here
API_SECRET=your_api_secret_here
# External Services
STRIPE_SECRET_KEY=sk_test_...
SENDGRID_API_KEY=SG...
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
# Feature Flags
ENABLE_FEATURE_X=true
ENABLE_FEATURE_Y=false
Environment-Specific Files
Multiple Environment Files
# .env.development
NODE_ENV=development
PORT=3000
DEBUG=true
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp_dev
API_BASE_URL=http://localhost:8000
# .env.staging
NODE_ENV=staging
PORT=3000
DEBUG=false
DATABASE_URL=postgresql://user:pass@staging-db:5432/myapp_staging
API_BASE_URL=https://staging-api.example.com
# .env.production
NODE_ENV=production
PORT=3000
DEBUG=false
DATABASE_URL=postgresql://user:pass@prod-db:5432/myapp_prod
API_BASE_URL=https://api.example.com
# .env.test
NODE_ENV=test
PORT=3001
DEBUG=false
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp_test
API_BASE_URL=http://localhost:8001
Node.js Environment Management
dotenv Package
Using dotenv
# Install dotenv
npm install dotenv
# Basic usage
require('dotenv').config();
# Load specific environment file
require('dotenv').config({ path: '.env.production' });
# Load multiple files
require('dotenv').config({ path: '.env' });
require('dotenv').config({ path: '.env.local' });
# Access environment variables
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
# Check if variable exists
if (!process.env.API_KEY) {
throw new Error('API_KEY is required');
}
# Type conversion
const port = parseInt(process.env.PORT, 10) || 3000;
const debug = process.env.DEBUG === 'true';
const maxConnections = Number(process.env.MAX_CONNECTIONS) || 10;
Environment Configuration Class
config.js
require('dotenv').config();
class Config {
constructor() {
this.nodeEnv = process.env.NODE_ENV || 'development';
this.port = parseInt(process.env.PORT, 10) || 3000;
this.debug = process.env.DEBUG === 'true';
// Database configuration
this.database = {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
name: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
};
// API configuration
this.api = {
baseUrl: process.env.API_BASE_URL,
key: process.env.API_KEY,
secret: process.env.API_SECRET,
};
// Redis configuration
this.redis = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
password: process.env.REDIS_PASSWORD,
};
// Feature flags
this.features = {
enableFeatureX: process.env.ENABLE_FEATURE_X === 'true',
enableFeatureY: process.env.ENABLE_FEATURE_Y === 'true',
};
this.validate();
}
validate() {
const required = [
'database.url',
'api.key',
'api.secret'
];
required.forEach(key => {
const value = this.getNestedValue(key);
if (!value) {
throw new Error(`Required environment variable not set: ${key}`);
}
});
}
getNestedValue(key) {
return key.split('.').reduce((obj, k) => obj && obj[k], this);
}
isDevelopment() {
return this.nodeEnv === 'development';
}
isProduction() {
return this.nodeEnv === 'production';
}
isTest() {
return this.nodeEnv === 'test';
}
}
module.exports = new Config();
Python Environment Management
python-dotenv
Using python-dotenv
# Install python-dotenv
pip install python-dotenv
# Basic usage
from dotenv import load_dotenv
import os
load_dotenv()
# Access environment variables
port = int(os.getenv('PORT', 3000))
debug = os.getenv('DEBUG', 'false').lower() == 'true'
database_url = os.getenv('DATABASE_URL')
# Load specific environment file
load_dotenv('.env.production')
# Load multiple files
load_dotenv('.env')
load_dotenv('.env.local')
# Check if variable exists
api_key = os.getenv('API_KEY')
if not api_key:
raise ValueError('API_KEY environment variable is required')
# Type conversion
port = int(os.getenv('PORT', '3000'))
max_workers = int(os.getenv('MAX_WORKERS', '4'))
debug = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
Pydantic Settings
settings.py
from pydantic import BaseSettings, Field
from typing import Optional
class Settings(BaseSettings):
# Application settings
app_name: str = Field(default="MyApp", env="APP_NAME")
debug: bool = Field(default=False, env="DEBUG")
port: int = Field(default=3000, env="PORT")
# Database settings
database_url: str = Field(..., env="DATABASE_URL")
db_host: str = Field(default="localhost", env="DB_HOST")
db_port: int = Field(default=5432, env="DB_PORT")
db_name: str = Field(..., env="DB_NAME")
db_user: str = Field(..., env="DB_USER")
db_password: str = Field(..., env="DB_PASSWORD")
# API settings
api_base_url: str = Field(..., env="API_BASE_URL")
api_key: str = Field(..., env="API_KEY")
api_secret: str = Field(..., env="API_SECRET")
# Redis settings
redis_host: str = Field(default="localhost", env="REDIS_HOST")
redis_port: int = Field(default=6379, env="REDIS_PORT")
redis_password: Optional[str] = Field(default=None, env="REDIS_PASSWORD")
# Feature flags
enable_feature_x: bool = Field(default=False, env="ENABLE_FEATURE_X")
enable_feature_y: bool = Field(default=False, env="ENABLE_FEATURE_Y")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = False
# Create settings instance
settings = Settings()
# Usage
print(f"App running on port {settings.port}")
print(f"Database: {settings.database_url}")
print(f"Debug mode: {settings.debug}")
Docker Environment Management
Docker Compose Environment
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "${PORT:-3000}:3000"
environment:
- NODE_ENV=${NODE_ENV:-development}
- DATABASE_URL=${DATABASE_URL}
- REDIS_HOST=redis
- REDIS_PORT=6379
env_file:
- .env
- .env.local
depends_on:
- db
- redis
db:
image: postgres:13
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "${DB_PORT:-5432}:5432"
redis:
image: redis:6-alpine
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
postgres_data:
Dockerfile Environment
Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Change ownership
RUN chown -R nextjs:nodejs /app
USER nextjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start application
CMD ["npm", "start"]
Security Best Practices
Secrets Management
Secure Configuration
# Never commit secrets to version control
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*.local" >> .gitignore
# Use environment-specific files
.env.example # Template file (safe to commit)
.env.development # Development secrets
.env.staging # Staging secrets
.env.production # Production secrets (never commit)
# Example .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
API_KEY=your_api_key_here
API_SECRET=your_api_secret_here
REDIS_PASSWORD=your_redis_password
# Use secret management services
# AWS Secrets Manager
# Azure Key Vault
# HashiCorp Vault
# Kubernetes Secrets
Environment Validation
Validation Script
#!/bin/bash
# Environment validation script
set -e
# Required environment variables
REQUIRED_VARS=(
"DATABASE_URL"
"API_KEY"
"API_SECRET"
"REDIS_HOST"
)
# Optional environment variables with defaults
OPTIONAL_VARS=(
"PORT:3000"
"DEBUG:false"
"LOG_LEVEL:info"
)
# Check required variables
check_required() {
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var:-}" ]; then
echo "ERROR: Required environment variable $var is not set"
exit 1
fi
done
}
# Set optional variables with defaults
set_optional() {
for var_default in "${OPTIONAL_VARS[@]}"; do
IFS=':' read -r var default <<< "$var_default"
if [ -z "${!var:-}" ]; then
export "$var"="$default"
echo "INFO: Set $var to default value: $default"
fi
done
}
# Validate environment
validate_environment() {
echo "Validating environment variables..."
check_required
set_optional
echo "All environment variables validated successfully"
}
# Run validation
validate_environment
CI/CD Environment Management
GitHub Actions Secrets
GitHub Actions Workflow
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
env:
NODE_ENV: test
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
- name: Build application
run: npm run build
env:
NODE_ENV: production
API_KEY: ${{ secrets.API_KEY }}
API_SECRET: ${{ secrets.API_SECRET }}
- name: Deploy to production
run: |
echo "Deploying to production..."
# Your deployment commands here
env:
NODE_ENV: production
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
API_SECRET: ${{ secrets.API_SECRET }}
REDIS_HOST: ${{ secrets.REDIS_HOST }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
Environment-Specific Configurations
Development Environment
Development Setup
# .env.development
NODE_ENV=development
PORT=3000
DEBUG=true
LOG_LEVEL=debug
# Local database
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp_dev
DB_HOST=localhost
DB_PORT=5432
# Local Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# Development API
API_BASE_URL=http://localhost:8000
API_KEY=dev_api_key
API_SECRET=dev_api_secret
# Feature flags for development
ENABLE_FEATURE_X=true
ENABLE_FEATURE_Y=true
ENABLE_DEBUG_PANEL=true
# Development tools
HOT_RELOAD=true
SOURCE_MAPS=true
Production Environment
Production Setup
# .env.production
NODE_ENV=production
PORT=3000
DEBUG=false
LOG_LEVEL=error
# Production database
DATABASE_URL=postgresql://user:pass@prod-db:5432/myapp_prod
DB_HOST=prod-db.internal
DB_PORT=5432
# Production Redis
REDIS_HOST=redis-cluster.internal
REDIS_PORT=6379
REDIS_PASSWORD=secure_redis_password
# Production API
API_BASE_URL=https://api.example.com
API_KEY=prod_api_key
API_SECRET=prod_api_secret
# Feature flags for production
ENABLE_FEATURE_X=true
ENABLE_FEATURE_Y=false
ENABLE_DEBUG_PANEL=false
# Production optimizations
HOT_RELOAD=false
SOURCE_MAPS=false
COMPRESSION=true
Summary
Environment variables management is crucial for secure and flexible application configuration:
- Use .env files: Organize configuration by environment
- Never commit secrets: Use .gitignore and secret management
- Validate configuration: Check required variables at startup
- Use type conversion: Convert strings to appropriate types
- Environment-specific configs: Separate dev, staging, and production
- Secure deployment: Use CI/CD secrets and environment variables
Need More Help?
Struggling with environment variables management or need help securing your application configuration? Our configuration experts can help you implement proper environment management practices.
Get Configuration Help