Cursor Rules for designing, validating and operating environment-variable based configuration across Node.js, React and containerised deployments.
Stop fighting configuration chaos. These Cursor Rules eliminate the guesswork, security gaps, and deployment headaches that plague JavaScript applications scaling across multiple environments.
You know the drill: dev works, staging breaks, production crashes at 2 AM because DATABASE_URL wasn't set. Your React app accidentally exposes API keys to browsers. Your team manually syncs .env files across services. Your Docker containers fail silently when secrets are missing.
The root problem? Most teams treat environment variables as an afterthought—scattered across codebases, inconsistently named, inadequately validated, and dangerously exposed.
These Cursor Rules transform environment variable management from a liability into a competitive advantage. Built on battle-tested patterns from production JavaScript applications, they establish:
// Scattered across your codebase
const dbUrl = process.env.DATABASE_URL || 'localhost:5432';
const apiKey = process.env.API_KEY; // Hope it's set!
const port = parseInt(process.env.PORT) || 3000;
// Single source of truth in /src/config/env.ts
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']),
APP_PORT: z.string().regex(/^\d+$/).transform(Number),
SECRET_DB_PASSWORD: z.string().min(16),
APP_DB_HOST: z.string().url().default('http://localhost'),
});
export const env = EnvSchema.parse(process.env);
// Type-safe, validated, single point of failure
Your CI pipeline now validates environment configuration before any deployment:
- name: Validate Environment
run: npm run env:validate
env:
NODE_ENV: production
# All required secrets injected here
Time saved: 4-6 hours per week previously spent debugging environment-related production issues.
Stop accidentally exposing server secrets to browsers:
// Server-only variables stay server-only
const serverEnv = {
SECRET_DB_PASSWORD: env.SECRET_DB_PASSWORD,
SECRET_JWT_KEY: env.SECRET_JWT_KEY,
};
// Client variables explicitly whitelisted
const clientEnv = {
REACT_APP_API_URL: env.REACT_APP_API_URL,
REACT_APP_VERSION: env.REACT_APP_VERSION,
};
Security impact: Zero accidental secret exposure in production builds.
Environment validation catches missing secrets before container startup:
# Validation runs first, container fails fast if misconfigured
RUN npm run env:validate
CMD ["node", "dist/server.js"]
Deployment reliability: 95% reduction in containers that start but immediately crash.
Automated secret synchronization with cloud providers:
# One command pulls all secrets from AWS Parameter Store
make secrets-pull
aws ssm get-parameters-by-path --path /myapp/prod/ --with-decryption \
| jq -r '.Parameters[] | "export \(.Name|split("/")|.[-1])=\(.Value)"' > .env
Operational efficiency: Secret rotation happens automatically via GitOps pipelines.
npm install zod dotenv-flow
npm install -D @types/node
Create /src/config/env.ts:
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']),
APP_PORT: z.string().regex(/^\d+$/).transform(Number),
APP_DB_HOST: z.string().url().optional().default('http://localhost'),
SECRET_DB_PASSWORD: z.string().min(16),
});
export const env = EnvSchema.parse(process.env);
Add to package.json:
{
"scripts": {
"env:validate": "ts-node src/config/env.ts --validate"
}
}
# .github/workflows/deploy.yml
- name: Validate Configuration
run: npm run env:validate
env:
NODE_ENV: production
APP_PORT: "8080"
SECRET_DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
// src/server.ts
import { env } from './config/env';
const app = express();
app.listen(env.APP_PORT, () => {
console.log(`Server running on port ${env.APP_PORT}`);
});
Development Velocity:
Security Posture:
Operational Reliability:
Team Productivity:
These rules don't just manage environment variables—they create a configuration management system that scales with your application. From local development to multi-region Kubernetes deployments, your configuration becomes a strategic advantage instead of a constant source of friction.
Your team will spend less time debugging configuration issues and more time building features. Your deployments will be more reliable. Your security posture will be stronger.
Ready to eliminate configuration chaos? Implement these Cursor Rules and transform how your JavaScript applications handle environment variables across their entire lifecycle.
You are an expert in JavaScript, TypeScript, Node.js, React, Docker, Kubernetes, GitHub Actions, HashiCorp Vault, AWS/GCP/Azure secret managers.
Key Principles
- Configuration *≠* code: keep secrets out of source control; ship the same artifact to every environment, vary only the config.
- Fail fast: validate env vars at process start-up; crash early if required variables are missing or invalid.
- Principle of Least Knowledge: expose only the vars a process truly needs; never leak server-side vars to the browser.
- Single Source of Truth: prefer central secret managers over scattered .env files. Sync locally with tooling, never by hand.
- Immutable infrastructure: treat env values as immutable for a running release; update via redeploy, not by `export`ing into a live shell.
- Name things clearly: SCOPE_PURPOSE_DETAIL (e.g. APP_DB_HOST). Prefix secrets with `SECRET_` for instant recognition.
- Encrypt everywhere, audit everything, rotate regularly.
JavaScript / TypeScript
- Keep *all* env interaction inside `/src/config/env.ts` (or `/lib/env.ts` for libraries); nothing else may read `process.env` directly.
- Use `dotenv-flow` or `dotenv-expand` only for *local* development; production must come from the platform (Docker/K8s/CI).
- Use a schema validator (e.g. `zod`, `@hapi/joi`, `envsafe`) to coerce + validate types:
```ts
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development','test','production']),
APP_PORT: z.string().regex(/^\d+$/).transform(Number),
SECRET_DB_PASSWORD: z.string().min(16),
APP_DB_HOST: z.string().url().optional().default('http://localhost'),
});
export const env = EnvSchema.parse(process.env);
```
- Never store binary blobs in env variables; use mounted files or secret volumes instead (e.g. PEM keys).
- Provide sane defaults *only* for non-critical vars; omit defaults for credentials to force explicit declaration.
- Use `.env.example` committed to git with every key documented; forbid `.env` via `.gitignore`.
Error Handling & Validation
- Validation lives in `env.ts`; throw a descriptive `ConfigError` when parsing fails.
- CI step: `node -r ts-node/register ./src/config/env.ts --validate` to fail the build when `.env.ci` is incomplete.
- Use early returns when computing derived config:
```ts
if (!env.FEATURE_FLAG) return doLegacy();
```
- Mask secrets in error logs: never include raw values—only variable names.
Framework-Specific Rules
• Node.js / Express
- Read `env` once at bootstrap; pass explicitly via dependency injection, *never* `import` global `env` inside route handlers.
- For multi-tenant apps, separate tenant data via prefix (e.g. TENANT_A_DB_HOST).
• React (Create React App / Vite / Next.js)
- Prefix public variables with `REACT_APP_` (CRA) or `VITE_` (Vite) / `NEXT_PUBLIC_` (Next.js) to whitelist exposure.
- Never reference server-only vars in the browser build. Use runtime injection (e.g. `window.__RUNTIME_CONFIG__`) for truly dynamic config.
• Docker / Docker Compose
- Store non-secret defaults in `docker-compose.yaml` using `${VAR:?err}` syntax to force override.
- Mount secret files with `type: secret` or use `docker secret create` for Swarm.
• Kubernetes
- Use `ConfigMap` for non-sensitive config, `Secret` (opaque/`kubernetes.io/dockerconfigjson`) for sensitive.
- Manage via GitOps (Argo CD/Flux); encrypt with SOPS + KMS to keep YAMLs in git.
- Reference via `envFrom` or mounted volumes; never hard-code in manifests.
• CI/CD (GitHub Actions / GitLab CI)
- Store secrets in the platform secret store; load via `env:` block scoped to job, never global.
- Add a `scripts/validate-env.mjs` step executed after checkout that ensures required vars are set.
• HashiCorp Vault / Cloud Secret Managers
- Read at startup with a short-lived token; cache in memory; renew leases programmatically.
- Use envelopes: keep ciphertext in env var, decrypt on read, drop plaintext ASAP.
Testing
- Provide `.env.test` committed to repo; keep values non-secret. Unit tests stub `process.env` via `cross-env NODE_ENV=test jest`.
- For integration in CI, inject secrets through the runner; never rely on `.env.test` for secrets.
- Use `dotenv-sniffer` (or custom script) in pre-commit to compare `.env` ↔ `.env.example` and forbid drift.
Performance & Scalability
- Avoid huge env payloads (>4 KB) on serverless platforms to reduce cold-start latency.
- Where thousands of vars are required, switch to mounted config files rather than envs.
- Rotate secrets automatically via platform hooks; redeploy apps to pick up new values (blue-green or rolling update).
Security
- Mark env variables containing secrets as `required` and `sensitive` in Infrastructure-as-Code (e.g. Terraform `sensitive = true`).
- Never echo env vars in build logs (`set +x` in bash). Mask using CI secrets redaction.
- Enable audit logging on secret reads; alert on anomalous access patterns.
- Enforce TTLs: use short-lived credentials (IAM roles, STS, GCP service account tokens) instead of long-lived secrets.
Documentation
- Maintain `CONFIGURATION.md` including: variable, description, type, default, environments, secret (y/n).
- Auto-generate from `env.ts` schema with `ts-node scripts/gen-doc.ts > CONFIGURATION.md`.
Common Pitfalls & How to Avoid Them
- Pitfall: Inconsistent prefixes across microservices → Solution: enforce lint rule `env-prefix/<service>` in ESLint custom plugin.
- Pitfall: Accidentally pushing `.env` to git → Solution: pre-commit hook scans diff with `git-secrets`.
- Pitfall: Loading `.env` in production image → Solution: multi-stage Docker build copies only compiled JS, not dev dotfiles.
Directory Conventions
- `config/` → framework-agnostic config loaders
- `config/env.ts` → schema + validation + export
- `scripts/` → helper CLIs (validate-env, rotate-secrets)
- `docs/CONFIGURATION.md` → auto-generated variable reference
Automation Recipes
- npm script `env:validate`: `ts-node src/config/env.ts --validate`
- GitHub Action:
```yaml
- name: Validate env
run: npm run env:validate
env:
NODE_ENV: production
# secrets injected here
```
- Makefile target:
```Makefile
secrets-pull:
aws ssm get-parameters-by-path --path /myapp/prod/ --with-decryption | jq -r '.Parameters[] | "export \(.Name|split("/")|.[-1])=\(.Value)"' > .env
```