
Node.js 环境变量:正确处理配置与机密的最佳实践
环境变量让你的应用知道它处于开发还是生产环境、数据库在哪里、以及使用哪些机密。如果处理不当,常常会导致 bug、安全漏洞和“在我机器上能跑”的问题。
基础:process.env
// 访问任何环境变量
const port = process.env.PORT;
const nodeEnv = process.env.NODE_ENV;
const dbUrl = process.env.DATABASE_URL;
// 始终为可选变量提供默认值
const port = process.env.PORT || 3000;
const logLevel = process.env.LOG_LEVEL || 'info';

设置 dotenv
本地开发最常用的方法:
npm install dotenv
# .env 文件(项目根目录)
PORT=3000
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=my-dev-secret-key
REDIS_URL=redis://localhost:6379
STRIPE_KEY=sk_test_xxxxx
// 在应用的最开始加载——在任何使用环境变量的其他导入之前
import 'dotenv/config'; // ESM
// 或者
require('dotenv').config(); // CommonJS
// 现在 process.env 已被填充
console.log(process.env.PORT); // 3000
关键:将 .env 添加到 .gitignore!
# .gitignore
.env
.env.local
.env.*.local
多环境文件
.env # 基础默认值(提交此文件——不含机密!)
.env.local # 本地覆盖(不要提交)
.env.development # 开发环境特定(如果不含机密可以提交)
.env.production # 生产环境(不要提交——使用真正的机密管理器)
.env.test # 测试环境
// 加载环境特定文件
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
require('dotenv').config({ path: envFile });
// 或者使用 dotenv-flow 自动处理
npm install dotenv-flow
require('dotenv-flow').config();
验证环境变量
绝不要让应用在缺少关键配置时启动!
// src/config/env.ts — 启动时验证
import { z } from 'zod';
const envSchema = z.object({
// 必需
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
// 必需且限定值
NODE_ENV: z.enum(['development', 'production', 'test']),
// 可选且有默认值
PORT: z.string().transform(Number).default('3000'),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
RATE_LIMIT_MAX: z.string().transform(Number).default('100'),
// 可选
REDIS_URL: z.string().url().optional(),
SENTRY_DSN: z.string().url().optional(),
});
// 验证——如果任何地方出错则抛出异常
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('❌ 无效的环境变量:');
console.error(parsed.error.flatten().fieldErrors);
process.exit(1); // 立即停止应用
}
export const env = parsed.data;
// env.PORT 现在类型为 number,而非 string
// 在整个应用中使用
import { env } from './config/env';
app.listen(env.PORT, () => {
console.log(`服务器运行在端口 ${env.PORT},模式为 ${env.NODE_ENV}`);
});
提供 .env.example 文件
始终提交一个模板,说明需要哪些变量:
# .env.example — 提交到版本控制
PORT=3000
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
JWT_SECRET=change-this-to-a-random-32-char-string
REDIS_URL=redis://localhost:6379
# 可选
SENTRY_DSN=
STRIPE_KEY=
新开发者运行 cp .env.example .env 并填入值。

安全最佳实践
绝不记录机密
// ❌ 绝不要这样做
console.log('启动配置:', process.env);
console.log('数据库 URL:', process.env.DATABASE_URL); // 泄露凭据
// ✅ 只记录安全的值
console.log('启动模式:', process.env.NODE_ENV);
console.log('服务器端口:', process.env.PORT);
console.log('数据库已连接:', !!process.env.DATABASE_URL); // 仅确认存在
使用强机密
# 生成加密随机机密
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# 输出:a1b2c3d4e5f6... (64 字符十六进制字符串)
# 或者使用你的令牌生成器工具
最小权限原则
# 为不同环境创建独立的数据库用户
# 开发:读写
# 生产:仅应用需要的权限
# CI/CD:可能只读用于测试运行

Docker 和容器环境
# Dockerfile — 绝不硬编码机密
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 不要在 Dockerfile 中使用 ENV 设置机密!
# 这些会被烘焙到镜像层中
# ENV JWT_SECRET=mysecret ❌
EXPOSE 3000
CMD ["node", "dist/server.js"]
# docker-compose.yml — 使用 env_file 或 environment
services:
api:
build: .
env_file:
- .env # 从文件加载
environment:
- NODE_ENV=production # 覆盖特定值
ports:
- "3000:3000"
生产环境机密管理
对于生产环境,避免在服务器上使用 .env 文件。使用合适的机密管理器:
| 平台 | 工具 |
|---|---|
| AWS | Secrets Manager / Parameter Store |
| Google Cloud | Secret Manager |
| Azure | Key Vault |
| Kubernetes | K8s Secrets + Sealed Secrets |
| Heroku/Railway | 平台仪表盘环境变量 |
| Doppler | 同步机密到任何平台 |
// 示例:AWS Secrets Manager
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getSecret(secretName) {
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretName })
);
return JSON.parse(response.SecretString);
}
// 启动时
const secrets = await getSecret('myapp/production/database');
process.env.DATABASE_URL = secrets.url;
常见模式
类型安全的配置对象
// 集中配置——单一事实来源
export const config = {
server: {
port: Number(process.env.PORT) || 3000,
env: process.env.NODE_ENV || 'development',
isProduction: process.env.NODE_ENV === 'production',
},
database: {
url: process.env.DATABASE_URL!,
poolSize: Number(process.env.DB_POOL_SIZE) || 10,
},
auth: {
jwtSecret: process.env.JWT_SECRET!,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
},
redis: {
url: process.env.REDIS_URL,
enabled: !!process.env.REDIS_URL,
},
} as const;
通过环境变量实现功能开关
const features = {
enableBetaUI: process.env.ENABLE_BETA_UI === 'true',
maxUploadSizeMB: Number(process.env.MAX_UPLOAD_SIZE_MB) || 10,
maintenanceMode: process.env.MAINTENANCE_MODE === 'true',
};
if (features.maintenanceMode) {
app.use((req, res) => {
res.status(503).json({ message: '服务暂时不可用' });
});
}
总结
| 规则 | 原因 |
|---|---|
绝不提交 .env |
包含真实机密 |
始终提交 .env.example |
记录所需配置 |
| 启动时验证 | 快速失败,错误清晰 |
| 生产环境使用机密管理器 | 安全、可审计、可轮换 |
| 绝不记录敏感值 | 日志会出现在许多地方 |
| 使用类型化配置对象 | 捕获拼写错误和类型错误 |
→ 使用 Token Generator 工具生成安全的随机机密。