What Are Environment Variables?
Environment variables are key-value pairs available to processes:
DATABASE_URL=postgres://localhost/mydb
API_KEY=sk-xxx123
DEBUG=true
They keep configuration separate from code.
Why Use Environment Variables?
Security
Secrets don't go in code:
# Bad - secret in code
api_key = "sk-xxx123"
# Good - secret in environment
api_key = os.environ.get("API_KEY")
Flexibility
Same code, different configs:
# Development
DATABASE_URL=postgres://localhost/dev
# Production
DATABASE_URL=postgres://prod-server/prod
Portability
Works everywhere:
- Local development
- CI/CD pipelines
- Cloud platforms
- Containers
Setting Environment Variables
In Shell
# Set for current session
export API_KEY=xxx
# Set for single command
API_KEY=xxx python script.py
# View current value
echo $API_KEY
# List all
env
In .env Files
# .env file
DATABASE_URL=postgres://localhost/mydb
API_KEY=sk-xxx123
DEBUG=true
Load with dotenv:
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ.get("API_KEY")
In Docker
# Dockerfile
ENV NODE_ENV=production
# docker-compose.yml
environment:
- DATABASE_URL=postgres://db/app
- API_KEY=${API_KEY}
# Or from file
env_file:
- .env
In Cloud Platforms
Most platforms have settings panels or CLI:
# Heroku
heroku config:set API_KEY=xxx
# Vercel
vercel env add API_KEY
# AWS Lambda
aws lambda update-function-configuration \
--function-name my-function \
--environment Variables={API_KEY=xxx}
Reading Environment Variables
Python
import os
# Get with default
debug = os.environ.get("DEBUG", "false")
# Required (raises error if missing)
api_key = os.environ["API_KEY"]
# Type conversion
port = int(os.environ.get("PORT", "8080"))
debug = os.environ.get("DEBUG", "false").lower() == "true"
JavaScript (Node.js)
// Get with default
const debug = process.env.DEBUG || 'false';
// Required
const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error('API_KEY required');
// Type conversion
const port = parseInt(process.env.PORT || '8080');
const debug = process.env.DEBUG === 'true';
Bash
# Get with default
debug=${DEBUG:-false}
# Required
if [ -z "$API_KEY" ]; then
echo "API_KEY required"
exit 1
fi
Common Environment Variables
Standard
NODE_ENV=production # Node.js environment
PYTHON_ENV=production # Python environment
DEBUG=false # Debug mode
LOG_LEVEL=info # Logging level
PORT=8080 # Server port
HOST=0.0.0.0 # Server host
TZ=UTC # Timezone
Database
DATABASE_URL=postgres://user:pass@host:5432/db
REDIS_URL=redis://localhost:6379
MONGO_URI=mongodb://localhost:27017/db
APIs
API_KEY=xxx
API_SECRET=yyy
API_BASE_URL=https://api.example.com
AWS
AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=yyy
AWS_REGION=us-east-1
AWS_PROFILE=default
Best Practices
Use Descriptive Names
# Good
DATABASE_URL=...
STRIPE_API_KEY=...
REDIS_CACHE_URL=...
# Bad
URL=...
KEY=...
DB=...
Provide Defaults for Non-Secrets
# Has sensible default
port = int(os.environ.get("PORT", "8080"))
log_level = os.environ.get("LOG_LEVEL", "info")
# No default for secrets
api_key = os.environ["API_KEY"] # Will fail if missing
Validate at Startup
def validate_config():
required = ["API_KEY", "DATABASE_URL", "SECRET_KEY"]
missing = [var for var in required if not os.environ.get(var)]
if missing:
raise ValueError(f"Missing required env vars: {missing}")
validate_config() # Call at startup
Don't Log Secrets
# BAD
logger.info(f"Using API key: {api_key}")
# GOOD
logger.info("API key configured: yes")
Use .env.example
Create a template without actual secrets:
# .env.example
DATABASE_URL=postgres://localhost/mydb
API_KEY=your-api-key-here
DEBUG=false
Never Commit .env
# .gitignore
.env
.env.local
.env.*.local
Common Patterns
Environment-Specific Config
env = os.environ.get("ENVIRONMENT", "development")
config = {
"development": {
"debug": True,
"database": "postgres://localhost/dev"
},
"production": {
"debug": False,
"database": os.environ["DATABASE_URL"]
}
}[env]
Feature Flags
FEATURE_NEW_UI=true
FEATURE_BETA_API=false
if os.environ.get("FEATURE_NEW_UI") == "true":
use_new_ui()
Connection Strings
# Full connection string
DATABASE_URL=postgres://user:pass@host:5432/db
# Or components
DB_HOST=localhost
DB_PORT=5432
DB_USER=myuser
DB_PASS=mypass
DB_NAME=mydb
Troubleshooting
Variable Not Set
# Check if set
echo $MY_VAR
# Check in Python
import os
print(os.environ.get("MY_VAR", "NOT SET"))
Wrong Value
# Check exact value (watch for quotes/spaces)
printenv MY_VAR
# In Python
print(repr(os.environ.get("MY_VAR")))
.env Not Loading
# Make sure dotenv is loaded first
from dotenv import load_dotenv
load_dotenv() # Call before accessing vars
# Check file path
load_dotenv(dotenv_path=".env")
Security Considerations
Secrets Management
For production:
- Use secrets managers (AWS Secrets Manager, Vault)
- Rotate credentials regularly
- Limit access to secrets
Never Expose in Logs or Errors
# BAD - might log the secret
raise Exception(f"Auth failed with key {api_key}")
# GOOD
raise Exception("Auth failed - check API_KEY configuration")
Different Secrets Per Environment
# Don't use production secrets in development
DEV_API_KEY=dev-only-key
PROD_API_KEY=prod-secret-key
Conclusion
Environment variables:
- Separate config from code
- Keep secrets secure
- Enable flexible deployment
- Work across platforms
Master them for clean, secure, portable applications.
Next: HTTP and REST APIs - Web API fundamentals