Variables d'environnement avec Claude Code : .env, Zod, Secrets et déploiement
Gérez les variables d'environnement et Secrets avec Claude Code : .env.example, Zod, CI/CD, redaction et rotation.
Quand on demande à Claude Code de créer une authentification, un paiement, un webhook ou une intégration IA, le premier vrai problème de production n’est pas toujours le composant UI. Il vient souvent de la configuration : une URL de base de données manquante, une clé de staging utilisée en production, un WEBHOOK_SECRET imprimé dans les logs CI, ou une vraie API key poussée sur GitHub.
Ce guide propose un modèle prêt à implémenter pour gérer les variables d’environnement et les Secrets avec Claude Code. Une variable d’environnement est une valeur donnée à l’application au démarrage, comme PORT ou APP_ORIGIN. Un Secret est une valeur exploitable si elle fuit, comme ANTHROPIC_API_KEY, DATABASE_URL ou WEBHOOK_SECRET. Un Secret peut être transmis via une variable d’environnement, mais il ne doit pas être traité comme une simple option.
Pour la configuration propre à Claude Code, consultez la référence officielle Claude Code environment variables. Pour valider la configuration applicative, nous utiliserons Zod. Côté CI/CD et déploiement, les références utiles sont GitHub Actions secrets, Vercel environment variables, Cloudflare Workers variables and secrets et Docker secrets. Les interfaces changent selon la plateforme, mais le principe reste le même : versionnez les noms et les règles, jamais les valeurs secrètes.
Pour compléter la posture de sécurité, lisez aussi les bonnes pratiques de sécurité Claude Code et l’authentification JWT avec Claude Code.
Considérez .env comme un contrat
Un fichier .env est pratique, mais il ne doit pas devenir le carnet privé de chaque développeur. Une équipe a besoin de trois couches :
- Déclaration :
.env.exampleliste les clés nécessaires - Validation : Zod bloque le démarrage si une valeur manque ou n’a pas le bon format
- Opérations : CI/CD et production injectent les valeurs depuis les Secrets de la plateforme
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
Le point important : toutes les entrées passent par le même schema. Claude Code doit recevoir les noms de clés, les règles de validation et le comportement en cas d’erreur, pas les vraies valeurs de production.
Cas d’usage concrets
| Cas | Variables typiques | Risque si cela échoue |
|---|---|---|
| Développement local | APP_ORIGIN, DATABASE_URL, ANTHROPIC_API_KEY | L’app ne fonctionne que sur une machine |
| Vérification de webhook | STRIPE_WEBHOOK_SECRET, WEBHOOK_SECRET | Des requêtes falsifiées sont acceptées |
| Tests CI | CI_DATABASE_URL, TEST_API_KEY | La PR passe mais le déploiement échoue |
| Production | DATABASE_URL, SESSION_SECRET, APP_ORIGIN | Mauvaise base, cookies invalides, fuite de credentials |
| Rotation de secrets | ANTHROPIC_API_KEY_NEXT | Une ancienne clé compromise reste valide |
Dans le workflow ClaudeCodeLab de Masa, le plus grand gain n’a pas été seulement de créer .env.example, mais de faire refuser le démarrage quand le contrat est incomplet. Les surprises de déploiement deviennent alors des erreurs visibles en pull request.
1. Séparez les fichiers
.env.example documente le projet, sans valeurs réelles. .env.local est propre à une machine. .env.production.example est une checklist de production, sans secrets.
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
Les placeholders ne sont pas des valeurs par défaut sûres. Ils indiquent simplement qu’une valeur doit être fournie ailleurs.
2. Validez avec Zod au démarrage
Dans Node.js, les variables d’environnement arrivent sous forme de chaînes. Même PORT=3000 est une chaîne ; z.coerce.number() la convertit et vérifie la plage.
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 ?? "",
};
}
Vérifiez localement :
cp .env.example .env.local
npx tsx src/config/env.ts
Demandez ensuite à Claude Code de centraliser les accès :
Trouve toutes les lectures directes de process.env dans ce dépôt.
Seul src/config/env.ts doit lire process.env directement.
Pour les autres fichiers, propose d'importer env depuis src/config/env.ts.
N'imprime aucun secret dans les logs, erreurs ou snapshots de test.
3. Masquez avant de logger
Les fuites ne viennent pas seulement de Git. Elles apparaissent dans les logs CI, les sorties de debug, l’error tracking, les captures d’écran ou les prompts collés dans 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));
Le masquage est une dernière protection. Le meilleur log est celui qui ne reçoit jamais le secret.
4. Injectez via CI/CD
GitHub Actions peut injecter des secrets de dépôt, d’environnement ou d’organisation. N’utilisez pas les credentials de production pour les tests de pull request ; créez des valeurs CI limitées.
# .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
Tous les workflows ne reçoivent pas les secrets de la même manière. Les PR depuis forks, les workflows réutilisables ou certains événements automatisés ont des limites. Gardez le job de validation explicite et n’écrivez pas les secrets dans des fichiers générés.
5. Docker, Vercel, Cloudflare
Dans Docker, n’inscrivez pas ENV API_KEY=... dans le Dockerfile. Pour un test local, un env file suffit ; en production, utilisez le secret store ou l’orchestrateur.
# local only
docker run --rm --env-file .env.local my-app:latest
Si votre runtime expose des secrets sous forme de fichiers, supportez la convention 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();
}
Sur Vercel, séparez Production, Preview et Development. Faites attention aux préfixes exposés au navigateur comme NEXT_PUBLIC_. Sur Cloudflare Workers, les valeurs arrivent via bindings, paramètre env ou Secrets de plateforme. Ne promettez pas une portabilité magique : documentez l’injection par plateforme et gardez le schema comme source de vérité.
Passe en revue la configuration d'environnement pour Vercel, Cloudflare et Docker.
Ne lis pas et ne demande pas les vraies valeurs de production.
Vérifie les clés obligatoires, public vs secret, build-time vs runtime, et les notes de rotation.
6. Rédigez un playbook de rotation
La rotation des Secrets ne doit pas être improvisée pendant un incident :
- Identifier le périmètre : service, environnement, permissions, responsable
- Créer une nouvelle valeur avec permissions minimales
- L’ajouter en
*_NEXTsi une double acceptation est possible - Déployer une compatibilité temporaire avec l’ancienne et la nouvelle valeur
- Basculer et vérifier les health checks
- Révoquer l’ancienne valeur
- Chercher l’exposition dans Git, CI, logs et prompts
- Mettre à jour
.env.exampleet les notes d’exploitation
Un webhook secret, une API key et un mot de passe de base ne se remplacent pas de la même façon. Documentez le propriétaire, la fenêtre et le rollback pour chaque type.
Échecs fréquents
| Échec | Cause | Correction |
|---|---|---|
.env committé | .gitignore ajouté trop tard | Révoquer les clés ; nettoyer Git ne suffit pas |
Secret dans NEXT_PUBLIC_ | Préfixe public mal compris | Séparer conventions public/private |
console.log(process.env) | Debug urgent | Redaction et revue des logs |
| Production ne démarre pas | Clé obligatoire absente | Exécuter src/config/env.ts en CI |
| Valeur locale dans build prod | Confusion build-time/runtime | Documenter l’injection par plateforme |
| Vraie clé collée dans Claude Code | Prompt confondu avec partage secret | Partager noms et règles, pas les valeurs |
Prompt prêt pour Claude Code
Implémente la gestion des variables d'environnement pour ce projet.
Exigences :
- Créer .env.example et .env.production.example
- Garder .env, .env.* et .dev.vars* hors de Git
- Ajouter un schema Zod dans src/config/env.ts et échouer au démarrage si une valeur manque
- Centraliser les lectures directes de process.env dans src/config/env.ts
- Masquer les secrets dans les logs de diagnostic
- Ajouter un job GitHub Actions qui valide l'environnement sur les pull requests
- Ajouter de courtes notes de déploiement pour Vercel, Cloudflare et Docker
Ne lis pas de vraies API keys ni d'URL de base de production. Travaille seulement avec les noms de clés et règles de validation.
Conclusion
Bien utiliser Claude Code pour la configuration ne consiste pas à coller des secrets dans le chat. Il faut lui faire implémenter un contrat : .env.example déclare les clés, Zod valide au démarrage, les logs sont masqués, CI/CD et la plateforme injectent les vraies valeurs, et l’équipe sait faire une rotation.
ClaudeCodeLab propose du conseil Claude Code, de la formation d’équipe, des revues de sécurité de dépôts et des templates pour l’authentification, les paiements, le CI/CD et les opérations de contenu. Si votre équipe veut accélérer sans exposer les clés de production, cette fondation est l’une des premières à standardiser.
Dans le dépôt de test de Masa, ce modèle a détecté avant déploiement une clé production manquante, un webhook secret susceptible d’apparaître dans les logs et un .env.example obsolète. La validation Zod au démarrage est simple, mais elle transforme la configuration en contrat vérifiable.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Échelle de sécurité des permissions Claude Code
Passer du read-only aux éditions limitées, preuves et checks de déploiement sans perdre le contrôle.
Claude Code Small PR Proof Pack : rendre les petits changements reviewables
Un pack de preuve pour PR Claude Code : diff, vérifications, URL publique, CTA et rollback.
Gate de review avant commit avec Claude Code
Review avant commit avec Claude Code : diff, build, URL publique, liens Gumroad, CTA consultation, tests manquants et fichiers hors scope.