Tips & Tricks (更新: 2026/6/2)

Claude Code 环境变量管理指南:.env、Zod、Secrets 与生产部署

用 Claude Code 安全管理环境变量和 Secrets:.env.example、Zod 校验、CI/CD 注入、脱敏与轮换。

Claude Code 环境变量管理指南:.env、Zod、Secrets 与生产部署

很多初学者让 Claude Code 做登录、支付、Webhook 或 AI 接口时,最早出问题的往往不是页面,而是配置。数据库 URL 少一个环境变量、把 staging key 用到了生产、Webhook secret 被打印进 CI 日志,或者真实 API key 被提交到 GitHub,都会让一个看似完成的功能变成安全事故。

本文给出一套可以直接落地的环境变量与 Secrets 管理模式。环境变量是运行时传给应用的配置值,例如 PORTAPP_ORIGIN。Secrets 是泄露后会被滥用的秘密值,例如 ANTHROPIC_API_KEYDATABASE_URLWEBHOOK_SECRET。Secrets 也常通过环境变量传入,但它们需要更严格的规则。

Claude Code 自身的配置请参考官方 Claude Code environment variables。应用侧校验用 Zod。CI/CD 与部署可对照官方文档:GitHub Actions secretsVercel environment variablesCloudflare Workers variables and secrets 以及 Docker secrets。不同平台的界面不同,但原则一致:代码里保存“需要哪些 key”,不要保存“真实 secret 值”。

如果要从整体安全角度补强,可以继续阅读 Claude Code 安全最佳实践Claude Code JWT 认证

把 .env 当成契约,而不是私人备忘录

.env 很方便,但不能变成每个人自己的小纸条。团队至少需要三层机制:

  • 声明:用 .env.example 列出项目需要哪些 key
  • 校验:应用启动时用 Zod 检查必填、URL 格式、最小长度和默认值
  • 运维:CI/CD 和生产环境从平台 Secrets 注入,不复制本地 .env
flowchart LR
  Dev["local .env.local"] --> Schema["Zod schema"]
  CI["GitHub Actions secrets"] --> Schema
  Prod["Vercel / Cloudflare / Docker secrets"] --> Schema
  Schema --> App["Type-safe app config"]
  Schema --> Logs["Redacted logs"]
  Example[".env.example"] --> Dev

这张图的重点是:所有入口都经过同一个 Zod schema。让 Claude Code 帮你实现时,也只给它 key 名、校验规则和失败时的行为,不要把真实生产值贴到 prompt 里。

典型使用场景

场景常见变量出错后果
本地开发APP_ORIGIN, DATABASE_URL, ANTHROPIC_API_KEY只有某个人电脑能运行,别人无法复现
Webhook 验签STRIPE_WEBHOOK_SECRET, WEBHOOK_SECRET接受伪造请求,订单或权限被篡改
CI 测试CI_DATABASE_URL, TEST_API_KEYPR 通过,但部署阶段失败
生产部署DATABASE_URL, SESSION_SECRET, APP_ORIGIN连错数据库、Cookie 域名错误、凭证泄露
Secret 轮换ANTHROPIC_API_KEY_NEXT旧 key 泄露后长期有效,影响范围扩大

Masa 在 ClaudeCodeLab 的小型 SaaS 项目里实践后发现,最有价值的不是单纯创建 .env.example,而是让应用在配置不完整时直接启动失败。这样部署事故会提前变成 PR 阶段可以审查的问题。

1. 先拆分文件

.env.example 是文档,不放真实值。.env.local 只属于本机。.env.production.example 是生产配置清单,也不放真实生产值。

mkdir -p src/config
touch .env.example .env.local .env.production.example src/config/env.ts
# .gitignore
.env
.env.*
!.env.example
!.env.production.example

# Cloudflare local secrets
.dev.vars
.dev.vars.*
# .env.example
APP_ENV=local
NODE_ENV=development
PORT=3000
APP_ORIGIN=http://localhost:3000
DATABASE_URL=postgresql://app:app@localhost:5432/app
ANTHROPIC_API_KEY=replace-with-local-dev-key
WEBHOOK_SECRET=replace-with-32-plus-character-secret
PUBLIC_ANALYTICS_KEY=
LOG_LEVEL=info
# .env.production.example
APP_ENV=production
NODE_ENV=production
PORT=3000
APP_ORIGIN=https://example.com
DATABASE_URL=<set-in-platform-secret-store>
ANTHROPIC_API_KEY=<set-in-platform-secret-store>
WEBHOOK_SECRET=<set-in-platform-secret-store>
PUBLIC_ANALYTICS_KEY=<optional-public-key>
LOG_LEVEL=info

注意:replace-with-local-dev-key 不是安全默认值,只是提醒“这里需要一个值”。生产环境必须从部署平台或 Secrets 管理器注入。

2. 用 Zod 在启动时校验

环境变量进入 Node.js 后都是字符串。PORT 看起来是数字,但实际也是字符串,所以需要 z.coerce.number() 转换并校验。

npm install zod dotenv
npm install -D tsx typescript @types/node
// src/config/env.ts
import "dotenv/config";
import { z } from "zod";

const secretNamePattern = /(SECRET|TOKEN|PASSWORD|API_KEY|DATABASE_URL|DSN)/i;

function redactValue(key: string, value: unknown): string {
  if (value === undefined || value === null || value === "") return "<empty>";
  const text = String(value);
  if (!secretNamePattern.test(key)) return text;
  if (text.length <= 8) return "<redacted>";
  return `${text.slice(0, 4)}...${text.slice(-4)}`;
}

const envSchema = z.object({
  APP_ENV: z.enum(["local", "development", "staging", "production"]).default("local"),
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  APP_ORIGIN: z.string().url(),
  DATABASE_URL: z.string().url(),
  ANTHROPIC_API_KEY: z.string().min(20, "ANTHROPIC_API_KEY is too short"),
  WEBHOOK_SECRET: z.string().min(32, "WEBHOOK_SECRET must be at least 32 characters"),
  PUBLIC_ANALYTICS_KEY: z.string().optional(),
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});

const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error("Environment validation failed:");
  for (const issue of parsed.error.issues) {
    const key = String(issue.path[0] ?? "unknown");
    console.error(`- ${key}: ${issue.message}; current=${redactValue(key, process.env[key])}`);
  }
  process.exit(1);
}

export const env = Object.freeze(parsed.data);
export type AppEnv = typeof env;

export function isProduction(): boolean {
  return env.APP_ENV === "production";
}

export function publicEnv() {
  return {
    APP_ENV: env.APP_ENV,
    APP_ORIGIN: env.APP_ORIGIN,
    PUBLIC_ANALYTICS_KEY: env.PUBLIC_ANALYTICS_KEY ?? "",
  };
}

本地检查:

cp .env.example .env.local
npx tsx src/config/env.ts

然后让 Claude Code 查找散落的 process.env

请找出仓库里所有直接读取 process.env 的位置。
除了 src/config/env.ts 以外,其他文件都应改为从 env 对象读取。
不要把 secret 打印到日志、错误信息或测试快照里。

3. 日志必须脱敏

Secret 泄露不只发生在 Git。CI 日志、调试输出、错误监控、截图、甚至贴给 Claude Code 的终端输出都可能泄露。准备一个脱敏工具:

// src/config/redact.ts
const sensitiveKeyPattern = /(SECRET|TOKEN|PASSWORD|API_KEY|DATABASE_URL|AUTH|COOKIE|PRIVATE)/i;

export function redactSecrets(input: Record<string, unknown>): Record<string, string> {
  return Object.fromEntries(
    Object.entries(input).map(([key, value]) => {
      if (value === undefined || value === null || value === "") return [key, "<empty>"];
      const text = String(value);
      if (!sensitiveKeyPattern.test(key)) return [key, text];
      return [key, text.length <= 10 ? "<redacted>" : `${text.slice(0, 4)}...${text.slice(-4)}`];
    }),
  );
}
import { env } from "./env";
import { redactSecrets } from "./redact";

console.info("Loaded config", redactSecrets(env));

脱敏只是最后一道保险。更好的做法是:日志设计时就不要把 secret 放进去。

4. CI/CD 从 Secrets 注入

GitHub Actions 可以把 repository、environment 或 organization secrets 注入 workflow。不要把生产凭证拿来跑普通 PR 测试,CI 应该使用权限更小、范围更窄的值。

# .github/workflows/env-check.yml
name: env-check

on:
  pull_request:
  push:
    branches: [main]

jobs:
  validate-env:
    runs-on: ubuntu-latest
    env:
      APP_ENV: development
      NODE_ENV: test
      PORT: 3000
      APP_ORIGIN: http://localhost:3000
      DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
      LOG_LEVEL: info
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
      - run: npm ci
      - name: Mask runtime-only values
        run: echo "::add-mask::$APP_ORIGIN"
      - run: npx tsx src/config/env.ts
      - run: npm test -- --runInBand

常见误区是以为所有 workflow 都能拿到 secrets。Fork PR、reusable workflow、Dependabot 等场景可能不同。把验证 job 写清楚,不要把 secret 写入生成文件。

5. Docker、Vercel、Cloudflare 的边界

Docker 中不要在 Dockerfile 里写 ENV API_KEY=...。本地测试可以用 env file,生产则应使用运行平台或 secret store。

# local only
docker run --rm --env-file .env.local my-app:latest

如果平台把 secret 作为文件挂载,可以支持 NAME_FILE 约定:

// src/config/secret-file.ts
import fs from "node:fs";

export function readEnvOrFile(name: string): string | undefined {
  const direct = process.env[name];
  if (direct) return direct;

  const filePath = process.env[`${name}_FILE`];
  if (!filePath) return undefined;
  return fs.readFileSync(filePath, "utf8").trim();
}

Vercel 需要区分 Production、Preview、Development;同时要注意 NEXT_PUBLIC_ 这类会暴露到浏览器的变量。Cloudflare Workers 通常通过 Worker binding、env 参数或平台 Secrets 提供值。不要把文章里的例子当成某个平台的唯一正确配置,真正的准绳是你的 schema 和官方文档。

请审查 Vercel、Cloudflare、Docker 的环境变量设置。
不要读取或索要真实生产值。
只检查必填 key、公开 key 与 secret key 的边界、build-time/runtime 的区别,以及是否缺少轮换说明。

6. 先写轮换 playbook

Secret 轮换不能等泄露之后才想。建议提前写好:

  1. 确认影响范围:服务、环境、权限、负责人
  2. 创建新值,尽量最小权限并设置有效期
  3. 可双写时先加 *_NEXT
  4. 短时间内让应用同时接受新旧值
  5. 部署并检查健康状态
  6. 失效旧值
  7. 搜索 Git 历史、CI 日志、监控日志和 prompt 历史
  8. 更新 .env.example 与运维文档

Webhook secret、API key、数据库密码的轮换方式不同。每一种都应写清负责人和回滚方式。

常见失败

失败原因对策
.env 被提交.gitignore 加得太晚立即吊销 key,不能只清理 Git 历史
secret 放进 NEXT_PUBLIC_不理解公开前缀用命名规则区分 public/private
console.log(process.env)临时调试太急使用脱敏工具,并做日志审查
生产启动失败平台缺少必填 keyCI 中先运行 src/config/env.ts
本地值进了生产构建混淆 build-time 与 runtime为每个平台写注入说明
把真实 key 贴给 Claude Code把实现请求当成 secret 分享只给 key 名和校验规则

可直接使用的 Claude Code prompt

请为这个项目实现环境变量管理。

要求:
- 创建 .env.example 和 .env.production.example
- .env, .env.*, .dev.vars* 不进入 Git
- 在 src/config/env.ts 中用 Zod schema 校验必填和格式
- 把直接读取 process.env 的逻辑集中到 src/config/env.ts
- 诊断日志必须对 secret 脱敏
- 添加 GitHub Actions job,在 PR 时执行环境变量校验
- 为 Vercel、Cloudflare、Docker 写简短部署说明

不要读取真实 API key 或生产数据库 URL,只根据 key 名和校验规则工作。

总结

Claude Code 环境变量管理的重点不是把 secret 贴给它,而是让它实现一套可验证的契约:.env.example 声明 key,Zod 在启动时校验,日志脱敏,CI/CD 与生产平台负责注入真实值,并提前准备轮换流程。

ClaudeCodeLab 提供 Claude Code 导入咨询、团队培训、仓库安全审查,以及认证、支付、CI/CD、内容运营相关模板。如果你的团队想让 Claude Code 更快落地,同时避免生产 key 外泄,环境变量管理是最值得先标准化的部分之一。

Masa 在测试仓库中应用这套模式后,部署前拦下了三个问题:生产 key 缺失、Webhook secret 可能进日志、.env.example 已过期。Zod 启动校验很简单,但它能把“只有某个人知道的配置”变成可执行的团队契约。

#Claude Code #环境变量 #Secrets #Zod #CI/CD #TypeScript
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。