Environment Variables: A Guide to Configuration Management
What Are Environment Variables?
Environment variables are key-value pairs injected at runtime to configure how applications behave without altering source code. They empower configuration flexibility across local development, CI/CD pipelines, containers, and cloud deployments.
Why Environment Variables?
| Purpose | Description |
|---|---|
| 🔐 Security | Secrets (e.g. API keys, DB creds) stay out of source code. |
| 🧱 Separation of concerns | Decouple config from application logic. |
| 🌀 Environment switching | Seamlessly change configs between dev, staging, production. |
| 🛠 Dynamic behaviour | Enable feature toggles, flags, and runtime settings. |
Who Needs to Manage Env Vars?
| Role | Usage Example |
|---|---|
| Developers | Local setup via .env
|
| DevOps Engineers | Inject env vars in containers, CI/CD workflows |
| Sysadmins | Set OS-level vars or orchestrate secrets |
| Security Teams | Manage secret stores and access control |
When Should You Use Env Vars?
Use them when:
- Switching between dev/staging/prod configurations
- Storing sensitive credentials
- Managing external service URLs
- Enabling/disabling feature flags
Avoid using env vars for static or non-sensitive content that won’t change between environments.
Common Mistakes
| ❌ Mistake | 💥 Risk / Issue |
|---|---|
Committing .env files |
Secrets leaked in public/private repos |
| Storing secrets in frontend code | Exposes API keys and tokens |
No .env.example
|
Hard for others to set up project |
| Missing defaults or fallbacks | App crashes without required variables |
Overloaded .env
|
Difficult to manage, prone to typo errors |
Using env() in Laravel outside config |
Caches won’t work as expected after deployment |
| Missing variable validation on boot | Harder to debug issues due to missing context |
Pros and Cons
| ✅ Pros | ❌ Cons |
|---|---|
| Simplifies configuration across environments | Flat structure—hard to organise without convention |
| Keeps secrets out of source code | Prone to accidental exposure if misused |
| Integrates seamlessly with CI/CD and containers | Lacks validation/type safety without additional tools |
| Enables dynamic runtime behaviour | Debugging missing vars can be challenging |
Best Practices by Context
Development
| Practice | Recommendation |
|---|---|
Use .env and .env.local
|
For personal overrides |
Include .env.example
|
With placeholders only |
.env in .gitignore
|
Always |
| Use dotenv loaders | E.g. vlucas/phpdotenv, dotenv, etc. |
Production
| Practice | Recommendation |
|---|---|
| Use secrets manager or CI/CD env injection | Never rely on .env in production |
| Avoid baking secrets into Docker images | Pass them at runtime instead |
| Inject through Kubernetes, Docker, etc. | Maintain clean separation |
Containers (Docker, Kubernetes)
| Context | Strategy |
|---|---|
| Docker | Use --env, --env-file, or docker-compose.yml
|
| Kubernetes | Use ConfigMap (non-sensitive) and Secret (sensitive), mount as env or files |
Do’s and ❌ Don’ts
| ✅ Do | ❌ Don’t |
|---|---|
Use .env.example
|
Share .env with real values |
| Validate required envs at app boot | Assume presence—use null fallbacks without warning |
| Document env usage | Leave other developers guessing |
| Store secrets securely | Hardcode them or expose in front-end |
Use consistent naming (APP_, DB_, etc.) |
Use vague or conflicting keys |
Framework-Specific Tips
⚙ Laravel
| 🔹 Tip | 🛠️ How to Use |
|---|---|
Use env() ONLY in config files |
Access with config('app.name'), not env()
|
Publish .env.example
|
To onboard teams easily |
| Use config caching in production | Run php artisan config:cache
|
⚙ Node.js (Express, NestJS)
| 🔹 Tip | 🛠️ How to Use |
|---|---|
Use dotenv to load .env
|
require('dotenv').config() |
Validate using packages like envalid
|
For type safety and default values |
| Never expose secrets in React/Vue apps | Use REACT_APP_* only for non-sensitive configs |
⚙ Python (Django, FastAPI)
| 🔹 Tip | 🛠️ How to Use |
|---|---|
Use python-dotenv or os.environ.get()
|
Load .env into environment |
Leverage pydantic in FastAPI |
Define env schema via BaseSettings
|
Use decouple for Django projects |
Clean separation of code and config |
⚙ Go
| 🔹 Tip | 🛠️ How to Use |
|---|---|
Use os.Getenv("KEY")
|
Native approach |
Consider packages like godotenv, viper
|
For dotenv support and defaults |
| Avoid panic on missing keys | Provide fallbacks or error out clearly |
⚙ Java (Spring Boot)
| 🔹 Tip | 🛠️ How to Use |
|---|---|
Use application.properties with env vars |
${ENV_VAR:default} |
Prefer @Value or @ConfigurationProperties
|
For injection and typing |
Use secrets or ConfigMap in Kubernetes |
Spring supports externalised config out-of-box |
⚙ Others (Ruby on Rails, .NET, etc.)
| Framework | Tip |
|---|---|
| Ruby on Rails | Use dotenv-rails, Figaro, or Rails.application.credentials
|
| .NET Core | Use IConfiguration to bind from environment or secrets |
🧪 Suggested Validation Strategy (General)
# Example shell script to check required envs before starting app
REQUIRED_VARS=("DB_HOST" "DB_USER" "DB_PASS" "APP_KEY")
for var in "${REQUIRED_VARS[@]}"
do
if [[ -z "${!var}" ]]; then
echo "❌ Missing required env variable: $var"
exit 1
fi
done
Or, use programmatic validation (e.g., pydantic, envalid, custom boot checkers).
📌 Final Notes for Teams
-
Onboarding: Share
.env.example, keep instructions up-to-date - Security: Rotate secrets, use proper access control
- Monitoring: Alert on missing/misconfigured env vars
- CI/CD: Never echo secrets in build logs
Photo by Bernd 📷 Dittrich on Unsplash