Skip to main content
DevOps & CloudSoftware Engineering

Environment Variables and Configuration Management

Manage application configuration properly across development, staging, and production with validation, type safety, and security.

Prince Mbeah Essilfie

Prince Mbeah Essilfie

May 4, 2026 at 06:04 AM

7 min read 1656 views
Environment Variables and Configuration Management

The Config Hierarchy

Configuration should flow from least to most specific:

  1. Defaults (in code) → lowest priority
  2. Config files (.env.local, config.yaml)
  3. Environment variables → highest priority

Validated Configuration with Zod

Never trust raw process.env:

// config/env.ts
import { z } from 'zod'

const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url().optional(),
  AUTH_SECRET: z.string().min(32),
  PORT: z.coerce.number().default(3000),
  SMTP_HOST: z.string().optional(),
  SMTP_PORT: z.coerce.number().optional(),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
})

export type Env = z.infer<typeof EnvSchema>

function validateEnv(): Env {
  const result = EnvSchema.safeParse(process.env)

  if (!result.success) {
    console.error('❌ Invalid environment variables:')
    console.error(result.error.format())
    process.exit(1)
  }

  return result.data
}

export const env = validateEnv()
Validate at startup. Fail fast with a clear error message listing exactly which variables are missing or invalid. Don't discover config issues at 3am under load.

Nuxt Runtime Config

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // Server-only (never exposed to client)
    databaseUrl: '',
    authSecret: '',

    // Public (available in client)
    public: {
      siteUrl: 'http://localhost:3000',
      apiBase: '/api',
    },
  },
})

Environment variables automatically override with NUXT_ prefix:

  • NUXT_DATABASE_URLruntimeConfig.databaseUrl
  • NUXT_PUBLIC_SITE_URLruntimeConfig.public.siteUrl

.env File Management

# .env.example (committed to git — documentation)
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
AUTH_SECRET=change-me-to-at-least-32-characters
REDIS_URL=redis://localhost:6379

# .env (NOT committed — local overrides)
DATABASE_URL=postgres://real:creds@prod-host:5432/prod

# .env.test (committed — test configuration)
DATABASE_URL=postgres://test:test@localhost:5432/test_db

The .env.example Pattern

#!/bin/bash
# scripts/check-env.sh — Run in CI to verify env is complete

required_vars=(
  "DATABASE_URL"
  "AUTH_SECRET"
  "REDIS_URL"
)

missing=()
for var in "${required_vars[@]}"; do
  if [ -z "${!var}" ]; then
    missing+=("$var")
  fi
done

if [ ${#missing[@]} -ne 0 ]; then
  echo "❌ Missing environment variables:"
  printf '  - %s\n' "${missing[@]}"
  exit 1
fi

Security Rules

  1. Never commit secrets — Use .gitignore, audit with git-secrets
  2. Different secrets per environment — Dev ≠ Staging ≠ Prod
  3. Rotate regularly — Especially after team changes
  4. Least privilege — Each service gets only the vars it needs
  5. Encrypt at rest — Use secrets managers (AWS SSM, Vault, Doppler)

Summary

Good config management: validate early, fail clearly, separate secrets from code, and make environments self-documenting with .env.example.

Node.jsDevOpsSecurity
Prince Mbeah Essilfie

Written by Prince Mbeah Essilfie

Fullstack Software Developer

Read next

The Complete Guide to Vue 3 Composables
12 min read

The Complete Guide to Vue 3 Composables

Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.

Prince Mbeah EssilfiePrince Mbeah Essilfie
1.1K