环境配置与多环境

What — 是什么

环境配置是 Node.js 应用管理不同运行环境(开发/测试/生产)配置的方式,通过环境变量和配置文件实现配置与代码分离,确保敏感信息不入库。

核心概念:

  • dotenv:从 .env 文件加载环境变量到 process.env,最简单的配置管理
  • node-config:分层配置系统,按 NODE_ENV 加载不同配置文件,支持合并覆盖
  • 环境变量process.env 中的配置,12-Factor App 推荐方式
  • 配置验证:启动时校验必要的环境变量是否存在和合法(zod/joi)
  • 密钥管理:敏感配置(数据库密码/API Key/JWT Secret)的管理策略
  • 多环境:development/staging/production 环境的配置差异化

关键特性:

  • .env 文件不提交到 Git(.gitignore 中排除)
  • .env.example 提交到 Git,记录必要的环境变量模板
  • dotenv 只在开发环境使用,生产环境由系统注入环境变量
  • node-config 支持配置继承(default.json → production.json 覆盖)
  • zod 在应用启动时验证环境变量,缺失或不合法直接报错

Why — 为什么

适用场景:

  • 所有需要区分环境的 Node.js 项目
  • 数据库连接字符串/API Key 等敏感配置
  • 多环境部署(开发/测试/预发/生产)

对比配置方案:

维度dotenvnode-config硬编码
配置分离
多环境手动切换自动加载代码判断
类型安全
验证需额外库需额外库
复杂度

优缺点:

  • ✅ 优点:配置与代码分离、敏感信息不入库、环境切换方便
  • ❌ 缺点:无类型安全、无变更通知、过多环境变量管理混乱

How — 怎么用

快速上手

npm install dotenv
// 开发环境加载 .env
if (process.env.NODE_ENV !== 'production') {
    require('dotenv').config();
}
# .env.example(提交到 Git)
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key-at-least-32-chars

代码示例

node-config 分层配置 + zod 验证:

// config/default.json
{
    "app": { "port": 3000, "name": "my-api" },
    "database": { "pool": { "min": 2, "max": 10 } },
    "redis": { "ttl": 3600 },
    "jwt": { "expiresIn": "7d" }
}

// config/production.json(覆盖 default)
{
    "app": { "port": 8080 },
    "database": { "pool": { "min": 5, "max": 50 } },
    "redis": { "ttl": 1800 }
}

// 使用
const config = require('config');
const port = config.get('app.port');
// env.ts — 环境变量验证
import { z } from 'zod';

const envSchema = z.object({
    NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
    PORT: z.coerce.number().min(1).max(65535).default(3000),
    DATABASE_URL: z.string().url(),
    REDIS_URL: z.string().url(),
    JWT_SECRET: z.string().min(32),
    JWT_EXPIRES_IN: z.string().default('7d'),
    LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info')
});

const env = envSchema.parse(process.env);
export default env;

常见问题与踩坑

问题原因解决方案
.env 泄露到 Git忘记加入 .gitignore.gitignore.env*,只提交 .env.example
环境变量类型不对process.env 全是字符串z.coerce.number()parseInt
Docker 中 .env 不生效容器不读 .env 文件docker run --env-file .env 或 Compose env_file
启动后才发现缺配置没有启动时验证用 zod 在 import 时验证

最佳实践

  • .env 不入库,.env.example 记录模板
  • 生产环境由 CI/CD 或 K8s 注入环境变量,不用 .env 文件
  • 启动时用 zod 验证必要环境变量
  • 敏感配置(密码/Key)只通过环境变量传递
  • 不同环境的差异化配置用 node-config 分层

面试题

Q1: 12-Factor App 对配置的原则是什么?

12-Factor 第三条:“在环境中存储配置”(Store config in the environment)。原则:① 配置与代码严格分离——配置不硬编码在代码中;② 配置通过环境变量注入——不同环境只需改变环境变量;③ 敏感信息不入代码库——数据库密码/API Key 等只存在于运行环境中。判断标准:如果代码开源,是否需要修改任何配置?如果不需要,说明配置分离做得好。

Q2: dotenv 的工作原理?生产环境为什么不用?

dotenv 读取 .env 文件,解析 KEY=VALUE 行,注入到 process.env。生产环境不用因为:① .env 文件是明文存储,服务器上不安全;② 容器化环境通过 docker run -e 或 K8s ConfigMap/Secret 注入更安全;③ 多实例部署时每个实例都要维护 .env 文件,不方便;④ CI/CD 流水线通过平台环境变量注入更可靠。dotenv 只用于本地开发便利。


相关链接: