Claude Code環境変数管理ガイド: .env、Zod、Secretsを本番運用まで安全に扱う
Claude Codeで環境変数とSecretsを安全に扱う実装ガイド。.env、Zod検証、CI/CD、ローテーションまで解説。
Claude Codeに「ログイン機能を作って」「Stripeをつないで」と頼むと、最初に壊れやすいのはUIではなく設定です。APIキー、DB接続文字列、Webhook署名キー、管理者メール、公開してよい分析ID。これらを同じ.envに雑に置くと、ローカルでは動いても本番で落ちるか、最悪の場合は秘密情報がGitHubやログに出ます。
この記事では、初心者でもそのまま使える環境変数とSecrets管理の型を作ります。環境変数は「実行時にアプリへ渡す設定値」、Secretsは「公開されたら悪用される秘密値」です。環境変数という入れ物は同じでも、扱い方は分けます。PUBLIC_ANALYTICS_KEYのような公開前提の値と、ANTHROPIC_API_KEYやDATABASE_URLのような秘密値を同じノリで扱わないことが出発点です。
Claude Code自体の環境変数は公式のClaude Code environment variablesにまとまっています。アプリ側の検証にはZodを使い、CI/CDではGitHub Actions secrets、デプロイ先ではVercel environment variables、Cloudflare Workers variables and secrets、Docker secretsの考え方を参照します。クラウドごとにUIやCLIは違いますが、「値の一覧をコード化し、秘密値は値そのものを保存しない」という原則は共通です。
関連する全体の守りはClaude Codeセキュリティ対策完全ガイド、認証まわりの秘密管理はClaude CodeでJWT認証を扱う方法も合わせて確認してください。
全体像: .envはメモ帳ではなく契約書
.envは便利ですが、ただのメモ帳として使うと破綻します。チームで必要なのは、次の3つです。
- 宣言: どの環境変数が必要かを
.env.exampleに書く - 検証: 起動時にZodで型、必須、URL形式、最小長をチェックする
- 運用: CI/CDと本番にはローカルの
.envをコピーせず、各サービスのSecrets機能から注入する
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に実装を任せるときも「本番の値を読ませる」のではなく、「必要なキー名、検証条件、失敗時の挙動」を読ませます。秘密値そのものはプロンプトに貼りません。
使えるユースケース
| ユースケース | 典型的な値 | 失敗すると起きること |
|---|---|---|
| ローカル開発 | APP_ORIGIN, DATABASE_URL, ANTHROPIC_API_KEY | 人によって動く/動かないが再現できない |
| Webhook検証 | STRIPE_WEBHOOK_SECRET, WEBHOOK_SECRET | 署名検証をスキップして不正リクエストを受ける |
| CIのテスト | CI_DATABASE_URL, TEST_API_KEY | PRでは通るのに本番デプロイで落ちる |
| 本番デプロイ | DATABASE_URL, SESSION_SECRET, APP_ORIGIN | 本番DB接続ミス、Cookie設定ミス、キー漏洩 |
| 秘密値のローテーション | ANTHROPIC_API_KEY_NEXT | 古いキーを無効化できず、漏洩後の影響範囲が広がる |
Masaが小規模SaaSの設定をClaude Codeで整理したとき、一番効いたのは「.env.exampleを作る」よりも「Zodで起動時に落とす」ことでした。キーが足りない状態で画面まで進ませないので、レビューやデプロイ前に事故が見つかります。
1. ファイル構成を分ける
まず、値を置く場所を分けます。.env.exampleには本物の値を書きません。.env.localは自分のPCだけに置きます。.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
初心者が混乱しやすい点は、.env.exampleのreplace-with-local-dev-keyも本番で使ってはいけないことです。これは「このキーが必要」という印であって、安全な初期値ではありません。
2. Zodで起動時に検証する
次に、アプリが読む環境変数を1ファイルに集約します。Zodは「文字列として渡ってくる値を、アプリが期待する型に変換して検査する道具」です。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
本物のアプリでは、src/config/env.ts以外でprocess.envを直接読まないルールにします。Claude Codeにレビューさせるときは、こう頼むと漏れを見つけやすくなります。
このリポジトリで process.env を直接読んでいる箇所を探してください。
src/config/env.ts 以外で読んでいる場合は、env から読む形に修正案を出してください。
秘密値はログ、エラー、テストスナップショットに出さないでください。
3. ログに秘密値を出さない
環境変数管理でよくある漏洩は、Gitコミットだけではありません。console.log(process.env)、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)}`];
}),
);
}
// example usage
import { env } from "./env";
import { redactSecrets } from "./redact";
console.info("Loaded config", redactSecrets(env));
ここで重要なのは、赤字化は最後の防波堤であって、秘密値を雑にログへ渡してよいという意味ではないことです。ログ設計の基本は「そもそも秘密値をログ対象に含めない」です。
4. CI/CDではSecretsから注入する
GitHub Actionsでは、Secretsに登録した値を${{ secrets.NAME }}で参照します。公式ドキュメントにもある通り、SecretsはフォークPRやDependabotなど一部の実行条件では渡されません。PRテストと本番デプロイで必要な値を分けてください。
# .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
ここでの落とし穴は、CI用の秘密値を本番と共用することです。CIはログ、再実行、外部コントリビューター、テスト用DBなどの事情が違います。CI_DATABASE_URLのようにスコープを狭くした値を用意します。
5. Docker、Vercel、Cloudflareでの注意点
Dockerでは、DockerfileにENV API_KEY=...と書かないでください。ビルド履歴やイメージメタデータに残る可能性があります。ローカル確認なら--env-fileで十分ですが、本番では実行基盤のSecretsやDocker secretsを使う方が安全です。
# local only
docker run --rm --env-file .env.local my-app:latest
Docker secretsのようにファイルとして渡される値を読む場合は、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のコードからはenvパラメータやSecretsを通じて値を受け取ります。Cloudflareのドキュメントも、機密情報には平文の変数ではなくSecretsを使うよう案内しています。
Claude Codeにデプロイ設定をレビューさせるときは、次のように境界を明示します。
Vercel/Cloudflare/Dockerの設定をレビューしてください。
本番値そのものは読まないでください。
確認してほしいのは、必須キーの一覧、公開してよいキー名、Secretsに入れるべきキー名、ビルド時/実行時の区別です。
6. ローテーション手順を先に書く
秘密値は「漏れたら差し替える」では遅いです。差し替える手順を先に決めます。
- 影響範囲を確認する: どのサービス、どの環境、どの権限か
- 新しい値を発行する: 可能なら権限を最小化し、有効期限をつける
*_NEXTとして追加する: 例ANTHROPIC_API_KEY_NEXT- アプリを一時的に新旧両対応にする: Webhook署名などは移行期間を持つ
- 本番へ反映して疎通確認する
- 古い値を失効させる
- Git履歴、CIログ、監視ログ、Claude Codeのプロンプト履歴を確認する
.env.exampleと運用メモを更新する
Webhookのように相手サービスが新旧署名を同時に送れる場合は、移行期間を短く設定します。DBパスワードのように切り替えが難しい値は、メンテナンス時間や接続プール再起動も手順に含めます。
7. よくある失敗と対策
| 失敗 | 原因 | 対策 |
|---|---|---|
.envをGitに入れた | .gitignoreが後から追加された | すぐにキーを無効化し、履歴削除だけで済ませない |
NEXT_PUBLIC_に秘密値を入れた | 公開プレフィックスの意味を知らない | 公開キーと秘密キーを命名規則で分ける |
console.log(process.env)した | デバッグを急いだ | redactSecretsとログレビューを入れる |
| 本番だけ起動しない | 必須キーが本番に未登録 | CIでenv.tsを実行し、デプロイ前に落とす |
| ローカル値で本番ビルドした | ビルド時と実行時を混同した | デプロイ先の環境ごとに値を分ける |
| Claude Codeに本物のキーを貼った | 実装依頼と秘密共有を混同した | キー名と検証条件だけを渡す |
Claude Codeは強力ですが、秘密値を貼れば「賢く隠してくれる」わけではありません。プロンプト、ターミナル出力、スクリーンショット、ログのどれも漏洩経路になります。
そのまま使えるClaude Code依頼文
このプロジェクトの環境変数管理を実装してください。
要件:
- .env.example と .env.production.example を作る
- .env, .env.*, .dev.vars* はGit管理外にする
- src/config/env.ts にZodスキーマを作り、起動時に必須値を検証する
- process.env の直接参照を src/config/env.ts に集約する
- 秘密値はログに出さず、必要なログは赤字化する
- GitHub Actionsで env.ts を実行して、PR時に設定漏れを検出する
- Vercel/Cloudflare/Dockerでの注入場所をREADMEに短く書く
本物のAPIキーや本番DB URLは読み込まず、キー名と検証条件だけで作業してください。
まとめ
Claude Codeで環境変数管理を任せるときの正解は、秘密値を渡すことではありません。.env.exampleで必要なキーを宣言し、Zodで起動時に検証し、ログでは赤字化し、CI/CDと本番では各プラットフォームのSecretsから注入する。ここまでをセットにすると、初心者チームでも「動くけれど危ない」状態から抜けられます。
ClaudeCodeLabでは、Claude Code導入支援、チーム向けトレーニング、既存リポジトリのセキュリティレビュー、環境変数テンプレート整備を相談できます。決済、認証、CI/CD、記事制作ワークフローまで含めて実装レベルで整えたい場合は、サイト内の問い合わせや教材から次のステップを選んでください。
この記事で紹介した内容を実際に試した結果、Masaの検証用リポジトリでは「本番だけ環境変数が足りない」「Webhook secretをログに出す」「.env.exampleが古い」という3つの問題をPR段階で止められました。特にZodの起動時検証は地味ですが効果が大きく、Claude Codeに修正を頼むときのレビュー基準にもなります。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Code権限セーフティラダー: 初心者がallowを広げる順番
Claude Codeの権限をread-onlyからbuild、限定編集、deploy確認まで段階的に広げる安全な運用手順。
Claude Code Small PR Proof Pack: 小さなPRをレビュー可能にする証拠セット
Claude Codeの小さなPRに、差分・検証・公開URL・CTA・rollbackを添える実務チェックリスト。
Claude Codeのコミット前レビューゲート: 差分、テスト、CTAをまとめて止める型
Claude Codeでcommit前に差分をレビューする実践手順。build、公開URL、CTA、Gumroadリンク、未翻訳本文を検知します。