Why Environment Variables Matter
Every app needs configuration: database URLs, API keys, feature flags. Hardcoding them is a recipe for disaster. Environment variables let you keep secrets out of your codebase and change behavior without redeploying. But using them carelessly can still leak secrets or break your app in production.
Here's how I handle env vars safely, from local development to deployment.
The Basics: Reading Env Vars
In Node.js, you read env vars from process.env. In Python, it's os.environ. But the safest way is to use a library that validates and typecasts them.
Node.js with dotenv and envalid
// .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
PORT=3000
// config.js
import dotenv from 'dotenv';
dotenv.config();
import { cleanEnv, str, num } from 'envalid';
const env = cleanEnv(process.env, {
DATABASE_URL: str(),
PORT: num({ default: 3000 }),
});
export default env;
envalid throws an error if a required variable is missing, and it converts types. No more parseInt(process.env.PORT) || 3000 scattered around.
Python with pydantic
# config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
port: int = 3000
class Config:
env_file = ".env"
settings = Settings()
Pydantic reads from .env and validates types. If DATABASE_URL is missing, it raises a clear error at startup.
Never Commit .env Files
Your .env file with real secrets should never hit version control. Add it to .gitignore immediately.
# .gitignore
.env
But you still need to share what variables are required. Commit a .env.example with dummy values.
# .env.example
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
PORT=3000
New team members copy it to .env and fill in real values.
Use a Secret Manager in Production
In production, don't rely on a .env file on the server. Use your cloud provider's secret manager (AWS Secrets Manager, GCP Secret Manager, or a tool like Vault). Inject secrets as environment variables at runtime.
For example, in a Docker container:
docker run -e DATABASE_URL=$(aws secretsmanager get-secret-value --secret-id mydb --query SecretString --output text) myapp
Or in Kubernetes, use a Secret resource:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
stringData:
DATABASE_URL: "postgres://user:pass@prod-db:5432/mydb"
Then reference it in your pod spec:
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: DATABASE_URL
This keeps secrets out of your image and out of your deployment YAML.
Validate Early and Fail Fast
Your app should crash on startup if a required env var is missing. That's better than running with undefined and failing later in a confusing way.
With the validation libraries above, you get that for free. But if you're using plain process.env, add a check at the top of your entry file.
const required = ['DATABASE_URL', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required env var: ${key}`);
}
}
Watch Out for Defaults
Defaults are useful, but they can hide problems. If you default to localhost, you might accidentally connect to your local database in production. I prefer to have no default for critical vars, and only default for non-essential ones like LOG_LEVEL.
Never Log Secrets
Logging env vars is an easy way to leak them. Make sure your logger doesn't dump the entire process.env or os.environ. If you need to debug, log the variable names but not the values.
console.log('DATABASE_URL is set:', !!process.env.DATABASE_URL);
Local Development Tips
- Use a
.env.localfor personal overrides and keep it gitignored. - Don't share
.envfiles over chat or email. Use a secure vault or a tool likedopplerordotenv-vaultif you need to share. - If you use Docker Compose, put env vars in a
env_filedirective, not inline in the YAML.
services:
app:
env_file:
- .env
Conclusion
Handling environment variables safely is about discipline:
- Centralize reading and validation.
- Keep secrets out of version control.
- Use a secret manager in production.
- Fail fast if something's missing.
- Never log secrets.
It's a small investment that pays off every time you onboard a developer, rotate a key, or debug a production issue. Your future self will thank you.
Top comments (0)