name: config-manager
description: Expert guide for managing application configuration including environment variables, config files, secrets management, and multi-environment setups. Use when handling .env files, config validation, or configuration architecture.
slug: config-manager
category: operations
complexity: complex
version: "1.0.0"
author: "id8Labs"
triggers:
- "config-manager"
- "config manager"
tags:
- development
- tool-factory-retrofitted---
Configuration Manager Skill
Core Workflows
Workflow 1: Primary Action
- Analyze the input and context
- Validate prerequisites are met
- Execute the core operation
- Verify the output meets expectations
- Report results
Overview
This skill helps you design and implement robust configuration management for applications. Covers environment variables, config files, secrets management, validation, type safety, and multi-environment deployments.
Configuration Philosophy
Twelve-Factor App Principles
- Store config in the environment: Separate config from code
- Strict separation: Config that varies between deploys should be in env vars
- No secrets in code: Ever. Period.
Configuration Hierarchy
Priority (highest to lowest):
1. Command-line arguments
2. Environment variables
3. Environment-specific config files (.env.local)
4. Default config files (.env)
5. Application defaults in code
What Goes Where
- Environment Variables: API keys, database URLs, feature flags
- Config Files: Non-sensitive defaults, complex structures
- Secrets Manager: Production credentials, API tokens
- Code: Application logic, never secrets
Environment Variables
.env File Structure
NODE_ENV=development
PORT=3000
APP_URL=http://localhost:3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
DATABASE_POOL_SIZE=10
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
RESEND_API_KEY=re_xxx
EMAIL_FROM=noreply@example.com
ENABLE_ANALYTICS=true
ENABLE_BETA_FEATURES=false
.gitignore Configuration
# Environment files
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
# Keep example file
!.env.example
Multi-Environment Setup
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/myapp_dev
LOG_LEVEL=debug
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/myapp_test
LOG_LEVEL=error
NODE_ENV=production
DATABASE_URL=${DATABASE_URL}
LOG_LEVEL=info
Type-Safe Configuration
Zod Schema Validation
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
APP_URL: z.string().url(),
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.coerce.number().min(1).max(100).default(10),
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
STRIPE_SECRET_KEY: z.string().startsWith('sk_').optional(),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_').optional(),
ENABLE_ANALYTICS: z.coerce.boolean().default(false),
ENABLE_BETA_FEATURES: z.coerce.boolean().default(false),
});
export type Env = z.infer<typeof envSchema>;
function loadEnv(): 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 = loadEnv();
Environment Validation at Build Time
import { z } from 'zod';
const clientEnvSchema = z.object({
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_APP_URL: z.string().url(),
});
const serverEnvSchema = z.object({
DATABASE_URL: z.string(),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().optional(),
});
const envSchema = clientEnvSchema.merge(serverEnvSchema);
export function validateEnv() {
if (typeof window !== 'undefined') {
return clientEnvSchema.parse({
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
});
}
return envSchema.parse(process.env);
}
T3 Env Pattern (Next.js)
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'test', 'production']),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
},
client: {
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(),
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});
Configuration Files
JSON Configuration
{
"app": {
"name": "My Application",
"version": "1.0.0"
},
"server": {
"port": 3000,
"host": "localhost"
},
"cache": {
"ttl": 3600,
"maxSize": 1000
},
"features": {
"darkMode": true,
"betaFeatures": false
}
}
{
"server": {
"host": "0.0.0.0"
},
"cache": {
"ttl": 86400
}
}
Config Loader
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { z } from 'zod';
const configSchema = z.object({
app: z.object({
name: z.string(),
version: z.string(),
}),
server: z.object({
port: z.number(),
host: z.string(),
}),
cache: z.object({
ttl: z.number(),
maxSize: z.number(),
}),
features: z.object({
darkMode: z.boolean(),
betaFeatures: z.boolean(),
}),
});
type Config = z.infer<typeof configSchema>;
function deepMerge(target: any, source: any): any {
const result = { ...target };
for (const key of Object.keys(source)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
export function loadConfig(): Config {
const env = process.env.NODE_ENV || 'development';
const configDir = join(process.cwd(), 'config');
const defaultPath = join(configDir, 'default.json');
let config = JSON.parse(readFileSync(defaultPath, 'utf-8'));
const envPath = join(configDir, `${env}.json`);
if (existsSync(envPath)) {
const envConfig = JSON.parse(readFileSync(envPath, 'utf-8'));
config = deepMerge(config, envConfig);
}
const localPath = join(configDir, 'local.json');
if (existsSync(localPath)) {
const localConfig = JSON.parse(readFileSync(localPath, 'utf-8'));
config = deepMerge(config, localConfig);
}
return configSchema.parse(config);
}
export const config = loadConfig();
Secrets Management
Local Development Secrets
op read "op://Development/MyApp/API_KEY"
doppler secrets download --no-file --format env > .env
aws ssm get-parameter --name "/myapp/dev/api-key" --with-decryption --query "Parameter.Value"
Production Secrets with Vercel
vercel env add STRIPE_SECRET_KEY production
vercel env add DATABASE_URL production
vercel env pull .env.local
Secrets in Docker
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
env_file:
- .env
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true
AWS Secrets Manager Integration
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
interface AppSecrets {
database_url: string;
stripe_secret_key: string;
jwt_secret: string;
}
let cachedSecrets: AppSecrets | null = null;
export async function getSecrets(): Promise<AppSecrets> {
if (cachedSecrets) {
return cachedSecrets;
}
const secretId = process.env.AWS_SECRET_ID || 'myapp/production/secrets';
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await client.send(command);
if (!response.SecretString) {
throw new Error('Secret not found');
}
cachedSecrets = JSON.parse(response.SecretString);
return cachedSecrets!;
}
async function connectDatabase() {
const secrets = await getSecrets();
return createConnection(secrets.database_url);
}
Feature Flags
Simple Feature Flag System
import { env } from './env';
export const features = {
analytics: env.ENABLE_ANALYTICS,
betaFeatures: env.ENABLE_BETA_FEATURES,
darkMode: true,
get isProduction() {
return env.NODE_ENV === 'production';
},
get enableDebugLogs() {
return env.NODE_ENV === 'development' || env.DEBUG === 'true';
},
} as const;
export function isEnabled(feature: keyof typeof features): boolean {
return Boolean(features[feature]);
}
if (isEnabled('analytics')) {
initAnalytics();
}
Environment-Aware Feature Flags
type Environment = 'development' | 'staging' | 'production';
interface FeatureConfig {
enabled: boolean | Environment[];
description: string;
}
const featureFlags: Record<string, FeatureConfig> = {
newCheckout: {
enabled: ['development', 'staging'],
description: 'New checkout flow',
},
aiAssistant: {
enabled: true,
description: 'AI assistant feature',
},
experimentalApi: {
enabled: ['development'],
description: 'Experimental API endpoints',
},
};
export function isFeatureEnabled(
feature: keyof typeof featureFlags
): boolean {
const config = featureFlags[feature];
const currentEnv = process.env.NODE_ENV as Environment;
if (typeof config.enabled === 'boolean') {
return config.enabled;
}
return config.enabled.includes(currentEnv);
}
Configuration Patterns
Singleton Configuration
import { env } from './env';
import { loadConfig } from './loader';
import { features } from './features';
class AppConfig {
private static instance: AppConfig;
public readonly env = env;
public readonly features = features;
public readonly settings = loadConfig();
private constructor() {
Object.freeze(this);
}
static getInstance(): AppConfig {
if (!AppConfig.instance) {
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
get isDevelopment(): boolean {
return this.env.NODE_ENV === 'development';
}
get isProduction(): boolean {
return this.env.NODE_ENV === 'production';
}
get databaseUrl(): string {
return this.env.DATABASE_URL;
}
}
export const config = AppConfig.getInstance();
Runtime Configuration Updates
import { EventEmitter } from 'events';
class RuntimeConfig extends EventEmitter {
private values: Map<string, any> = new Map();
get<T>(key: string, defaultValue?: T): T | undefined {
return this.values.get(key) ?? defaultValue;
}
set<T>(key: string, value: T): void {
const oldValue = this.values.get(key);
this.values.set(key, value);
this.emit('change', { key, oldValue, newValue: value });
}
onChange(callback: (change: { key: string; oldValue: any; newValue: any }) => void) {
this.on('change', callback);
return () => this.off('change', callback);
}
}
export const runtimeConfig = new RuntimeConfig();
runtimeConfig.set('maintenanceMode', true);
runtimeConfig.onChange(({ key, newValue }) => {
if (key === 'maintenanceMode' && newValue) {
showMaintenanceBanner();
}
});
Validation Checklist
Environment Setup
Security
Developer Experience
Multi-Environment
When to Use This Skill
Invoke this skill when:
- Setting up configuration for a new project
- Adding new environment variables
- Implementing secrets management
- Creating feature flag systems
- Debugging configuration issues
- Setting up multi-environment deployments
- Migrating configuration between providers
- Implementing runtime configuration changes