Environment Variables Explained
Managing Configuration & Secrets the Right Way
By Locallhost.dev Team •
Updated: January 2026 •
10 min read
Config should be strictly separated from code. This is Principle III of the "12-Factor App" methodology. Why? Because you might commit your code to GitHub, but you definitely don't want to commit your AWS Secret Keys or Database Passwords.
What are Environment Variables?
They are key-value pairs that exist outside your application, provided by the Operating System or the Container (Docker) running your app.
# Examples of Env Vars
PORT=3000
HOST=localhost
NODE_ENV=development
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
API_KEY=sk_live_123456789
Why Use Them?
1. Security
Never hardcode secrets. If you put const apiKey = "123" in your code and push it to a public repo, bots will find it in seconds.
2. Flexibility (Write Once, Run Anywhere)
The code shouldn't change between environments. Only the config should.
- Local: Connects to local DB (
localhost:5432)
- Staging: Connects to staging DB (
db-staging.aws.com)
- Production: Connects to prod DB (
db-prod.aws.com)
The code remains: connect(process.env.DATABASE_URL)
The .env File Standard
In development, setting system-wide variables is annoying. Enter the dotenv pattern. You create a file named .env in your project root:
# .env file content
PORT=8080
DB_HOST=localhost
DB_USER=admin
DB_PASS=secretPassword123
CRITICAL: Add .env to your .gitignore file immediately! Never commit this file.
Instead, commit a file named .env.example that contains the keys but NOT the values:
# .env.example (Safe to commit)
PORT=8080
DB_HOST=
DB_USER=
DB_PASS=
Setting Env Vars by OS
Windows (PowerShell)
# Temporary (current session)
$env:PORT = "3000"
# Persistent (user-level)
setx PORT "3000"
macOS / Linux (bash/zsh)
# Temporary (current shell)
export PORT=3000
# Persistent (add to ~/.zshrc or ~/.bashrc)
export PORT=3000
Note: Persistent variables require a new terminal session to take effect.
Precedence Rules (Who Wins?)
When multiple sources define the same variable, precedence matters.
- Runtime/Process Env (highest priority)
- Framework-specific files (e.g.,
.env.local)
- Base .env
- Default values in code (lowest priority)
Practical tip: In Next.js, .env.local overrides .env, and NEXT_PUBLIC_* variables are exposed to the browser.
Accessing Env Vars in Code
Node.js / JavaScript / Next.js
// 1. Install dotenv: npm install dotenv
// 2. Load it early
require('dotenv').config();
// 3. Access
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
console.log(`Server starting on port ${port}`);
Python
import os
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv()
port = os.getenv("PORT", 3000) # Defaults to 3000 if not set
api_key = os.environ.get("API_KEY")
Java (Spring Boot)
Spring Boot loads application.properties which can reference env vars:
server.port=${PORT:8080}
spring.datasource.url=${DATABASE_URL}
Environment Variables in CI/CD
Production environments should inject variables via deployment platforms, not files.
- GitHub Actions: Use repository secrets and
${{ secrets.MY_KEY }}.
- Vercel/Netlify: Set variables in the dashboard for each environment.
- Docker: Use
-e flags or environment: in Compose.
Common Pitfalls
- Type confusion: Everything is a string. Parse booleans and numbers explicitly.
- Missing defaults: A missing variable can crash the app at startup.
- Leaking secrets: Never expose secrets to client-side code.
- Inconsistent naming: Stick to uppercase + underscores.
Best Practices Summary
- ✅ Always add
.env to .gitignore.
- ✅ Use
UPPER_SNAKE_CASE for variable names.
- ✅ Provide defaults in your code if the variable is optional.
- ✅ In Production (AWS, Heroku, Vercel), set these variables in the platform dashboard, not in a file.
- ✅ Use
.env.local, .env.test for framework-specific overrides (like in Next.js).