PureTools

Environment Variables: The Developer's Guide

PureTools Team· 8 min read
Environment Variables: The Developer's Guide

Environment Variables: Configuration Done Right

Hardcoded API keys, database URLs, and feature flags are the #1 cause of security incidents in small teams. Environment variables solve this by separating configuration from code. Your app reads DATABASE_URL instead of containing postgres://admin:password123@prod-db:5432.

Why Environment Variables

The 12-factor app methodology says it best: store config in the environment. Benefits:

  • Security: Secrets aren't in your git history
  • Flexibility: Same code runs in dev, staging, and prod with different configs
  • Simplicity: No complex config file formats to parse

The .env File

Most frameworks support a .env file at the project root:

# .env — NEVER commit this file
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key-here
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_API_URL=https://api.example.com
NODE_ENV=development
PORT=3000

Load it in your app:

// Node.js — dotenv (most common)
import 'dotenv/config';
// or
require('dotenv').config();

console.log(process.env.DATABASE_URL);
// "postgres://user:pass@localhost:5432/mydb"
# Python — python-dotenv
from dotenv import load_dotenv
import os

load_dotenv()
db_url = os.getenv('DATABASE_URL')

Naming Conventions

ConventionExampleNotes
SCREAMING_SNAKE_CASEDATABASE_URLUniversal standard for env vars
Prefix with serviceSTRIPE_SECRET_KEYGroups related vars
Prefix with NEXT_PUBLIC_NEXT_PUBLIC_API_URLNext.js: exposed to browser
Prefix with VITE_VITE_API_URLVite: exposed to browser
Prefix with REACT_APP_REACT_APP_API_URLCRA: exposed to browser

Critical: Only variables with the framework-specific prefix are exposed to the browser. Never put secrets in NEXT_PUBLIC_ or VITE_ variables — they'll be in your JavaScript bundle.

Environment File Hierarchy

Most frameworks load env files in this priority (later overrides earlier):

.env                # Default values, committed to repo (no secrets!)
.env.local          # Local overrides, gitignored
.env.development    # Development-specific
.env.production     # Production-specific
.env.test           # Test-specific

Secrets in CI/CD

Never put real secrets in .env files that get deployed. Use your CI/CD platform's secrets management:

# GitHub Actions
name: Deploy
on: push
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
    steps:
      - run: npm run deploy
# Docker Compose
services:
  app:
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
    # Or from a file:
    env_file:
      - .env.production

Validation at Startup

Don't wait for a runtime crash to discover a missing env var. Validate at startup:

// TypeScript with zod
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', 'production', 'test']),
});

export const env = envSchema.parse(process.env);
// Throws immediately if any variable is missing or invalid
# Python with pydantic
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    jwt_secret: str
    port: int = 3000
    debug: bool = False

    class Config:
        env_file = '.env'

settings = Settings()  # Validates on instantiation

Common Mistakes

  • Committing .env to git. Add it to .gitignore before your first commit. Check with git log -- .env.
  • Using env vars for structured data. ALLOWED_ORIGINS=http://localhost:3000,https://myapp.com works, but for complex config, use a config file that reads env vars.
  • Different .env formats across tools. Some tools need quotes around values, some don't. KEY="value" and KEY=value behave differently in shell vs dotenv.
  • Forgetting to restart. Env vars are read at process start. Changing .env requires restarting the server.

Manage your env files: Env Formatter — sort, deduplicate, and validate your .env files.