environment-config

Environment Configuration

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "environment-config" with this command: npx skills add dadbodgeoff/drift/dadbodgeoff-drift-environment-config

Environment Configuration

Centralized, validated environment variables that fail fast at startup.

When to Use This Skill

  • Starting a new project that needs env var management

  • Environment variables scattered across codebase

  • Missing vars causing runtime crashes

  • Need different configs for dev/staging/prod

  • Want type-safe access to configuration

Core Concepts

  • Centralized config - Single source of truth for all env vars

  • Fail fast - Validate at startup, not when first accessed

  • Type safety - Full TypeScript/Python typing for all config values

  • Environment separation - Clear distinction between dev/staging/prod

File Structure

project/ ├── .env # Local development (gitignored) ├── .env.example # Template (committed) ├── .env.production # Production overrides (gitignored or in CI) ├── .env.local # Local overrides (gitignored) └── src/ └── lib/ └── env.ts # Validation and exports

TypeScript Implementation

With Zod Validation

// lib/env.ts import { z } from 'zod';

/**

  • Server-side environment variables.
  • These are NOT exposed to the browser. */ const serverSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),

// Database DATABASE_URL: z.string().url(),

// External services API_URL: z.string().url().default('http://localhost:8787'), REDIS_URL: z.string().url().optional(),

// Feature flags ENABLE_ANALYTICS: z.coerce.boolean().default(false), ENABLE_RATE_LIMITING: z.coerce.boolean().default(true),

// Secrets JWT_SECRET: z.string().min(32),

// Logging LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), });

/**

  • Client-side environment variables.
  • MUST be prefixed with NEXT_PUBLIC_ (Next.js) or VITE_ (Vite) */ const clientSchema = z.object({ NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_APP_URL: z.string().url().optional(), });

// Validate at module load time const serverEnv = serverSchema.safeParse(process.env); const clientEnv = clientSchema.safeParse(process.env);

if (!serverEnv.success) { console.error('❌ Invalid server environment variables:'); console.error(JSON.stringify(serverEnv.error.flatten().fieldErrors, null, 2)); throw new Error('Invalid server environment configuration'); }

if (!clientEnv.success) { console.error('❌ Invalid client environment variables:'); console.error(JSON.stringify(clientEnv.error.flatten().fieldErrors, null, 2)); throw new Error('Invalid client environment configuration'); }

/**

  • Type-safe server environment.
  • Use this in API routes and server components. */ export const env = serverEnv.data;

/**

  • Type-safe client environment.
  • Use this in client components. */ export const publicEnv = clientEnv.data;

// Type exports export type Env = z.infer<typeof serverSchema>; export type PublicEnv = z.infer<typeof clientSchema>;

Without Dependencies (Lightweight)

// lib/env.ts - No external dependencies

function requireEnv(key: string): string { const value = process.env[key]; if (!value) { throw new Error(Missing required environment variable: ${key}); } return value; }

function optionalEnv(key: string, defaultValue: string): string { return process.env[key] || defaultValue; }

function boolEnv(key: string, defaultValue: boolean): boolean { const value = process.env[key]; if (value === undefined) return defaultValue; return value === 'true' || value === '1'; }

function intEnv(key: string, defaultValue: number): number { const value = process.env[key]; if (value === undefined) return defaultValue; const parsed = parseInt(value, 10); if (isNaN(parsed)) { throw new Error(Environment variable ${key} must be a number); } return parsed; }

export const env = { // Required DATABASE_URL: requireEnv('DATABASE_URL'), JWT_SECRET: requireEnv('JWT_SECRET'),

// Optional with defaults API_URL: optionalEnv('API_URL', 'http://localhost:8787'), LOG_LEVEL: optionalEnv('LOG_LEVEL', 'info'), PORT: intEnv('PORT', 3000),

// Booleans ENABLE_ANALYTICS: boolEnv('ENABLE_ANALYTICS', false), ENABLE_RATE_LIMITING: boolEnv('ENABLE_RATE_LIMITING', true),

// Computed isDev: process.env.NODE_ENV === 'development', isProd: process.env.NODE_ENV === 'production', isTest: process.env.NODE_ENV === 'test', } as const;

// Validate on import Object.keys(env); // Triggers all getters

Python Implementation

With Pydantic

config/settings.py

from pydantic_settings import BaseSettings from pydantic import Field, field_validator from typing import Literal from functools import lru_cache

class Settings(BaseSettings): """Application settings with validation."""

# Environment
environment: Literal["development", "production", "test"] = "development"
debug: bool = False

# Database
database_url: str = Field(..., description="PostgreSQL connection string")

# External services
api_url: str = "http://localhost:8787"
redis_url: str | None = None

# Feature flags
enable_analytics: bool = False
enable_rate_limiting: bool = True

# Secrets
jwt_secret: str = Field(..., min_length=32)

# Logging
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"

@field_validator("database_url")
@classmethod
def validate_database_url(cls, v: str) -> str:
    if not v.startswith(("postgresql://", "postgres://")):
        raise ValueError("database_url must be a PostgreSQL connection string")
    return v

@property
def is_dev(self) -> bool:
    return self.environment == "development"

@property
def is_prod(self) -> bool:
    return self.environment == "production"

class Config:
    env_file = ".env"
    env_file_encoding = "utf-8"
    case_sensitive = False

@lru_cache def get_settings() -> Settings: """Cached settings instance. Call once at startup.""" return Settings()

Validate on import

settings = get_settings()

Without Dependencies

config/settings.py

import os from dataclasses import dataclass from typing import Optional

class ConfigError(Exception): """Raised when configuration is invalid.""" pass

def require_env(key: str) -> str: """Get required environment variable or raise.""" value = os.getenv(key) if not value: raise ConfigError(f"Missing required environment variable: {key}") return value

def optional_env(key: str, default: str) -> str: """Get optional environment variable with default.""" return os.getenv(key, default)

def bool_env(key: str, default: bool) -> bool: """Get boolean environment variable.""" value = os.getenv(key) if value is None: return default return value.lower() in ("true", "1", "yes")

def int_env(key: str, default: int) -> int: """Get integer environment variable.""" value = os.getenv(key) if value is None: return default try: return int(value) except ValueError: raise ConfigError(f"Environment variable {key} must be an integer")

@dataclass(frozen=True) class Settings: """Immutable application settings."""

# Required
database_url: str
jwt_secret: str

# Optional
api_url: str
log_level: str
port: int

# Feature flags
enable_analytics: bool
enable_rate_limiting: bool

# Computed
is_dev: bool
is_prod: bool
is_test: bool

def load_settings() -> Settings: """Load and validate settings from environment.""" return Settings( database_url=require_env("DATABASE_URL"), jwt_secret=require_env("JWT_SECRET"), api_url=optional_env("API_URL", "http://localhost:8787"), log_level=optional_env("LOG_LEVEL", "INFO"), port=int_env("PORT", 8000), enable_analytics=bool_env("ENABLE_ANALYTICS", False), enable_rate_limiting=bool_env("ENABLE_RATE_LIMITING", True), is_dev=os.getenv("ENVIRONMENT", "development") == "development", is_prod=os.getenv("ENVIRONMENT") == "production", is_test=os.getenv("ENVIRONMENT") == "test", )

Validate on import

settings = load_settings()

Usage Examples

TypeScript

// In API routes import { env } from '@/lib/env';

export async function GET() { if (env.ENABLE_ANALYTICS) { await trackEvent('api_call'); }

const response = await fetch(${env.API_URL}/data); return Response.json(await response.json()); }

// In client components import { publicEnv } from '@/lib/env';

const apiClient = createClient(publicEnv.NEXT_PUBLIC_API_URL);

Python

In FastAPI

from config.settings import settings

@app.get("/health") async def health(): return { "status": "healthy", "environment": settings.environment, "debug": settings.debug, }

In services

from config.settings import settings

async def process_data(): if settings.enable_analytics: await track_event("data_processed")

.env.example Template

=============================================================================

Required - App will not start without these

=============================================================================

DATABASE_URL=postgresql://user:pass@localhost:5432/myapp JWT_SECRET=your-secret-key-at-least-32-characters-long

=============================================================================

External Services

=============================================================================

API_URL=http://localhost:8787 REDIS_URL=redis://localhost:6379

=============================================================================

Feature Flags

=============================================================================

ENABLE_ANALYTICS=false ENABLE_RATE_LIMITING=true

=============================================================================

App Config

=============================================================================

NODE_ENV=development LOG_LEVEL=debug PORT=3000

.gitignore

Environment files

.env .env.local .env.*.local .env.production .env.staging

Keep the example

!.env.example

Docker Integration

Build args for client-side vars (baked into bundle)

ARG NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

Runtime vars set via docker-compose or k8s

docker-compose.yml

services: app: build: . environment: - NODE_ENV=production - DATABASE_URL=${DATABASE_URL} - JWT_SECRET=${JWT_SECRET} env_file: - .env.production

Best Practices

  • Validate at startup - Never let invalid config reach runtime

  • Use typed access - No raw process.env calls in business logic

  • Separate client/server - Client vars need special prefixes

  • Default sensibly - Dev-friendly defaults, prod requires explicit config

  • Document everything - .env.example is your config documentation

Common Mistakes

  • Committing .env files with secrets

  • Using process.env directly throughout codebase

  • Not validating at startup (crashes at 3am instead)

  • Exposing server secrets to client bundle

  • Missing .env.example (new devs can't onboard)

Related Skills

  • Feature Flags

  • Error Handling

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

oauth-social-login

No summary provided by upstream source.

Repository SourceNeeds Review
General

sse-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

multi-tenancy

No summary provided by upstream source.

Repository SourceNeeds Review