Environment variables configure your application without changing code. Get them wrong and you leak secrets or break deployments.
The Basics
# Set a variable
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
# Use in your application
echo $DATABASE_URL
# Set for a single command
DATABASE_URL="postgres://..." node server.js.env Files for Development
# .env (never commit this!)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
JWT_SECRET=dev-secret-change-in-production
STRIPE_SECRET_KEY=sk_test_xxxxx
PORT=3000
NODE_ENV=developmentLoad with dotenv:
// Node.js
import 'dotenv/config';
console.log(process.env.DATABASE_URL);# Python
from dotenv import load_dotenv
load_dotenv()
import os
print(os.environ['DATABASE_URL']).env.example (DO commit this)
# .env.example — copy to .env and fill in values
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
REDIS_URL=redis://localhost:6379
JWT_SECRET=
STRIPE_SECRET_KEY=
PORT=3000
NODE_ENV=development.gitignore
.env
.env.local
.env.production
.env*.localDocker
# docker-compose.yml
services:
app:
image: my-app
environment:
- NODE_ENV=production
- PORT=3000
env_file:
- .env.production# Dockerfile — don't bake secrets into images!
# Bad:
ENV DATABASE_URL=postgresql://user:pass@db/mydb
# Good — set at runtime:
# docker run -e DATABASE_URL="..." my-appMaster this topic with hands-on labs
Go beyond reading — build real projects in sandboxed environments with expert video guidance.
Browse Courses →Kubernetes ConfigMaps and Secrets
Non-sensitive configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: "production"
PORT: "3000"
LOG_LEVEL: "info"Sensitive values:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
DATABASE_URL: "postgresql://user:pass@db:5432/mydb"
JWT_SECRET: "your-production-secret"Use in deployment:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secretsValidation
Validate at startup — fail fast:
// config.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'staging', 'production']),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
});
export const env = envSchema.parse(process.env);
// Throws immediately if any variable is missing or invalidCI/CD
GitHub Actions
env:
NODE_ENV: test
jobs:
test:
runs-on: ubuntu-latest
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
steps:
- run: npm test
env:
JWT_SECRET: ${{ secrets.JWT_SECRET }}Vercel / Netlify
Set in dashboard: - Production: real API keys, production database - Preview: test API keys, staging database - Development: local defaults
Get weekly IT automation tips
Docker, Ansible, Terraform, MLOps — curated insights delivered to your inbox. No spam.
Subscribe Free →Common Mistakes
1. Committing .env Files
# Check if .env was ever committed
git log --all -- .env
# Remove from history (if committed)
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch .env' HEAD2. Using Secrets in Docker Build Args
# Bad — args are visible in image history
ARG DATABASE_URL
RUN echo $DATABASE_URL > /app/config
# docker history shows the secret!3. Different Names Across Environments
# Dev
DB_HOST=localhost
# Production
DATABASE_HOST=db.internal # Different name!Pick one name and use it everywhere.
4. No Default Values
// Bad — crashes silently with wrong behavior
const port = process.env.PORT; // undefined → app binds to nothing
// Good — explicit default
const port = parseInt(process.env.PORT || '3000', 10);Hierarchy
When multiple sources exist, this is the standard precedence:
- Command-line flags (highest)
- Environment variables
.env.local(git-ignored).env(committed defaults)- Application defaults (lowest)
Secrets Management for Production
| Tool | Best For |
|---|---|
| AWS Secrets Manager | AWS-native apps |
| HashiCorp Vault | Multi-cloud, enterprise |
| Kubernetes Secrets | K8s-native (basic) |
| External Secrets Operator | K8s + external vault |
| SOPS | Encrypted files in Git |
| Doppler | SaaS secrets manager |
What's Next?
Our Docker Fundamentals course covers container configuration best practices. Node.js REST APIs teaches production-grade environment handling. First lessons are free. -e ---
Ready to go deeper? Explore our hands-on DevOps courses — practical labs covering Docker, Ansible, Terraform, and more.
Ready to learn by doing?
Stop reading tutorials — start building. Expert video courses with hands-on labs in real sandboxed environments.
Related Articles
Git for DevOps Engineers
Essential Git workflows for DevOps: branching strategies, interactive rebasing, cherry-picking, bisect debugging, and CI/CD integration.
Docker Compose for Dev Environments
Set up reproducible local development environments with Docker Compose. Multi-service stacks, hot reload, database seeding, and team workflows.
DevContainers for Team Development
Dev Containers standardize development environments using Docker. Learn how to set up devcontainers for your team with VS Code, GitHub Codespaces, and custom.
External Secrets Operator Guide
External Secrets Operator syncs secrets from AWS Secrets Manager, HashiCorp Vault, and Azure Key Vault into Kubernetes Secrets. Learn how to stop committing.
Falco Runtime Security Kubernetes
Falco detects runtime threats in Kubernetes using eBPF. Learn how to set up Falco for container security monitoring, write custom rules, and integrate.
Fedora 43: Modern Linux Desktop
Fedora 43 delivers cutting-edge packages with GNOME 48, Wayland by default, and a polished developer experience for desktop Linux.
Explore topics
Browse more articles on the topics covered here.