environment-config
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEnvironment 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
- 集中式配置 - 所有环境变量的单一可信来源
- 快速失败 - 在启动时验证,而非首次访问时
- 类型安全 - 为所有配置值提供完整的TypeScript/Python类型支持
- 环境隔离 - 清晰区分开发/预发布/生产环境
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 exportsproject/
├── .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 exportsTypeScript Implementation
TypeScript实现
With Zod Validation
基于Zod的验证实现
typescript
// 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>;typescript
// 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)
无依赖实现(轻量版)
typescript
// 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 getterstypescript
// 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 gettersPython Implementation
Python实现
With Pydantic
基于Pydantic的实现
python
undefinedpython
undefinedconfig/settings.py
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()
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
Validate on import
settings = get_settings()
undefinedsettings = get_settings()
undefinedWithout Dependencies
无依赖实现
python
undefinedpython
undefinedconfig/settings.py
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: booldef 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",
)
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: booldef 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
Validate on import
settings = load_settings()
undefinedsettings = load_settings()
undefinedUsage Examples
使用示例
TypeScript
TypeScript
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);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
Python
python
undefinedpython
undefinedIn FastAPI
In FastAPI
from config.settings import settings
@app.get("/health")
async def health():
return {
"status": "healthy",
"environment": settings.environment,
"debug": settings.debug,
}
from config.settings import settings
@app.get("/health")
async def health():
return {
"status": "healthy",
"environment": settings.environment,
"debug": settings.debug,
}
In services
In services
from config.settings import settings
async def process_data():
if settings.enable_analytics:
await track_event("data_processed")
undefinedfrom config.settings import settings
async def process_data():
if settings.enable_analytics:
await track_event("data_processed")
undefined.env.example Template
.env.example模板
bash
undefinedbash
undefined=============================================================================
=============================================================================
Required - App will not start without these
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
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
JWT_SECRET=your-secret-key-at-least-32-characters-long
=============================================================================
=============================================================================
External Services
External Services
=============================================================================
=============================================================================
API_URL=http://localhost:8787
REDIS_URL=redis://localhost:6379
API_URL=http://localhost:8787
REDIS_URL=redis://localhost:6379
=============================================================================
=============================================================================
Feature Flags
Feature Flags
=============================================================================
=============================================================================
ENABLE_ANALYTICS=false
ENABLE_RATE_LIMITING=true
ENABLE_ANALYTICS=false
ENABLE_RATE_LIMITING=true
=============================================================================
=============================================================================
App Config
App Config
=============================================================================
=============================================================================
NODE_ENV=development
LOG_LEVEL=debug
PORT=3000
undefinedNODE_ENV=development
LOG_LEVEL=debug
PORT=3000
undefined.gitignore
.gitignore配置
gitignore
undefinedgitignore
undefinedEnvironment files
Environment files
.env
.env.local
.env.*.local
.env.production
.env.staging
.env
.env.local
.env.*.local
.env.production
.env.staging
Keep the example
Keep the example
!.env.example
undefined!.env.example
undefinedDocker Integration
Docker集成
dockerfile
undefineddockerfile
undefinedBuild args for client-side vars (baked into bundle)
Build args for client-side vars (baked into bundle)
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
Runtime vars set via docker-compose or k8s
Runtime vars set via docker-compose or k8s
```yaml
```yamldocker-compose.yml
docker-compose.yml
services:
app:
build: .
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.production
undefinedservices:
app:
build: .
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.production
undefinedBest Practices
最佳实践
- Validate at startup - Never let invalid config reach runtime
- Use typed access - No raw calls in business logic
process.env - Separate client/server - Client vars need special prefixes
- Default sensibly - Dev-friendly defaults, prod requires explicit config
- Document everything - is your config documentation
.env.example
- 启动时验证 - 绝不让无效配置进入运行阶段
- 使用类型化访问 - 业务逻辑中不要直接调用
process.env - 区分客户端/服务端 - 客户端变量需要特殊前缀
- 合理设置默认值 - 开发环境使用友好的默认值,生产环境需要显式配置
- 全面文档化 - 就是你的配置文档
.env.example
Common Mistakes
常见错误
- Committing files with secrets
.env - Using directly throughout codebase
process.env - Not validating at startup (crashes at 3am instead)
- Exposing server secrets to client bundle
- Missing (new devs can't onboard)
.env.example
- 提交包含敏感信息的文件
.env - 在整个代码库中直接使用
process.env - 未在启动时验证配置(导致凌晨3点崩溃)
- 将服务端敏感信息暴露给客户端打包文件
- 缺失(新开发者无法快速上手)
.env.example
Related Skills
相关方案
- Feature Flags
- Error Handling
- 功能开关
- 错误处理