E

Environment & Config Manager

Manages environment variables, validates .env files, generates typed config objects, and ensures consistency across environments.

SkillClipticsdevopsv1.0.0MIT
0 views0 copies

Environment & Config Manager

Manage application configuration across development, staging, and production environments with type-safe access, secret handling, and validation at startup.

When to Use This Template

Choose Environment & Config Manager when:

  • Your app reads from .env files and you need type validation and defaults
  • You have multiple environments (dev, staging, prod) with different configurations
  • You want to fail fast at startup if required environment variables are missing
  • Secrets management needs separation from regular configuration

Consider alternatives when:

  • You need enterprise secrets management (use AWS Secrets Manager or HashiCorp Vault)
  • Your configuration is purely infrastructure-level (use Terraform variables)
  • You need runtime feature flags (use LaunchDarkly or Unleash)

Quick Start

# .claude/skills/environment-config-manager.yml name: environment-config-manager description: Manage typed environment config with validation prompt: | Create or update environment configuration management. Requirements: 1. Type-safe config access (no raw process.env) 2. Validation at startup — fail fast on missing required vars 3. Defaults for optional values 4. Separate secret handling (never logged or serialized) 5. Environment-specific overrides 6. Generate .env.example from config schema Use Zod or similar for schema validation.

Example invocation:

claude "Set up typed environment config for the API with database, Redis, and Stripe settings"

Generated config module:

// src/config/index.ts import { z } from 'zod'; const configSchema = z.object({ NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'), PORT: z.coerce.number().default(3000), // Database DATABASE_URL: z.string().url(), DATABASE_POOL_SIZE: z.coerce.number().min(1).max(100).default(10), // Redis REDIS_URL: z.string().url().optional(), // Stripe (secrets — never logged) STRIPE_SECRET_KEY: z.string().startsWith('sk_'), STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'), }); export const config = configSchema.parse(process.env); export type Config = z.infer<typeof configSchema>;

Core Concepts

Configuration Layers

LayerPrioritySourceExample
DefaultsLowestSchema definitionPORT: 3000
.env fileLow.env, .env.localDevelopment overrides
EnvironmentMediumprocess.env, container envStaging/prod values
CLI flagsHighest--port 8080Debug overrides

Type-Safe Configuration with Zod

import { z } from 'zod'; import dotenv from 'dotenv'; // Load .env files based on NODE_ENV const envFile = process.env.NODE_ENV === 'test' ? '.env.test' : '.env'; dotenv.config({ path: envFile }); const envSchema = z.object({ // Server NODE_ENV: z.enum(['development', 'staging', 'production', 'test']), PORT: z.coerce.number().default(3000), HOST: z.string().default('0.0.0.0'), LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), // Database DATABASE_URL: z.string().url(), DB_SSL: z.coerce.boolean().default(false), DB_POOL_MIN: z.coerce.number().default(2), DB_POOL_MAX: z.coerce.number().default(10), // Auth JWT_SECRET: z.string().min(32), JWT_EXPIRES_IN: z.string().default('7d'), SESSION_SECRET: z.string().min(32), // External Services SMTP_HOST: z.string().optional(), SMTP_PORT: z.coerce.number().default(587), SMTP_USER: z.string().optional(), SMTP_PASS: z.string().optional(), // Feature Flags ENABLE_SIGNUP: z.coerce.boolean().default(true), MAINTENANCE_MODE: z.coerce.boolean().default(false), }); // Validate at startup const parsed = envSchema.safeParse(process.env); if (!parsed.success) { console.error('Invalid environment configuration:'); for (const issue of parsed.error.issues) { console.error(` ${issue.path.join('.')}: ${issue.message}`); } process.exit(1); } export const env = parsed.data; // Secret masking for logging const SECRET_KEYS = ['JWT_SECRET', 'SESSION_SECRET', 'SMTP_PASS', 'DATABASE_URL']; export function getSafeConfig(): Record<string, string> { return Object.fromEntries( Object.entries(env).map(([key, value]) => [ key, SECRET_KEYS.includes(key) ? '***REDACTED***' : String(value) ]) ); }

Environment File Generation

// Generate .env.example from schema function generateEnvExample(schema: z.ZodObject<any>): string { const lines: string[] = ['# Environment Configuration', '']; const shape = schema.shape; for (const [key, zodType] of Object.entries(shape)) { const isOptional = zodType.isOptional(); const defaultVal = zodType._def?.defaultValue?.(); const description = zodType.description || ''; let example = ''; if (defaultVal !== undefined) example = String(defaultVal); else if (key.includes('URL')) example = 'https://...'; else if (key.includes('SECRET') || key.includes('KEY')) example = 'your-secret-here'; else example = ''; lines.push(`# ${description || key}${isOptional ? ' (optional)' : ' (required)'}`); lines.push(`${key}=${example}`); lines.push(''); } return lines.join('\n'); }

Configuration

OptionTypeDefaultDescription
schemastring"zod"Validation library: zod, joi, yup, custom
envFilesstring[][".env", ".env.local"]Dotenv files to load (in order)
failOnMissingbooleantrueExit process if required vars are missing
maskSecretsbooleantrueRedact secret values in logs
generateExamplebooleantrueAuto-generate .env.example file
secretPrefixstring[]["SECRET_", "KEY_", "TOKEN_"]Patterns to treat as secrets

Best Practices

  1. Validate all config at startup, not at usage — Parse and validate the entire environment configuration when the application boots. If STRIPE_SECRET_KEY is missing, fail immediately with a clear error message rather than crashing 3 hours later when a user tries to check out.

  2. Never use process.env directly in application code — Access configuration through the validated config object. This guarantees type safety, ensures defaults are applied, and creates a single source of truth. Search your codebase for raw process.env usage periodically.

  3. Keep .env.example in version control, never .env — The .env.example file documents every configuration variable with descriptions and safe example values. New developers copy it to .env and fill in real values. Add .env* (except .env.example) to .gitignore.

  4. Separate secrets from regular config — Secrets (API keys, database passwords, JWT secrets) should be loaded from a secure source in production (Vault, AWS Secrets Manager, encrypted env vars) rather than plain .env files. Your config module should support multiple sources transparently.

  5. Use environment-specific defaults wisely — Development defaults should enable local development without any .env file (e.g., SQLite database, localhost URLs). Production should have no defaults for critical values — force explicit configuration to prevent accidental misconfiguration.

Common Issues

Boolean environment variables parse incorrectlyprocess.env.ENABLE_FEATURE is the string "false", which is truthy in JavaScript. Use Zod's z.coerce.boolean() or explicitly check value === 'true' rather than relying on JavaScript truthiness. This is the most common source of config bugs.

Dotenv files override production environment variables — If dotenv.config() runs in production, it can override environment variables set by the deployment platform. Only load .env files in development, or use dotenv.config({ override: false }) to ensure system environment variables take precedence over file values.

Config changes require application restart — Environment variables are read once at startup and cached. If you change a value in production, the running process still uses the old value. For values that need runtime updates without restarts, use a config service (like Consul or a database-backed config) with a polling mechanism.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates