type: decision
status: active
timestamp: 2026-06-22
tags: [decision, env, secrets, sops, razorpay, vite, astro]

Three-env file split — .env / .env.development / .env.production

Three env files per NODE_ENV. Sops-encrypted. Loaded via Vite/Astro

Three-env file split

Decision

Replace single .env with a three-file split keyed off NODE_ENV. Key NAMES stay identical across files; only VALUES differ. Apps read the same process.env.RAZORPAY_KEY_ID in either env.

The three files

FileLoaded whenContents
.envalways (defaults)Shared defaults + non-environment-specific values + public keys (PUBLIC_FIREBASE_API_KEY, PUBLIC_FIREBASE_AUTH_DOMAIN, etc.)
.env.developmentNODE_ENV=developmentRazorpay TEST keys, Razorpay TEST plan IDs, dev URLs (http://localhost:4321, dev.oriz.in), NODE_ENV=development
.env.productionNODE_ENV=productionRazorpay LIVE keys, LIVE plan IDs, prod URLs (https://oriz.in), NODE_ENV=production

SOPS encryption

All three files are encrypted at rest:

.env             ? .env.enc
.env.development ? .env.development.enc
.env.production  ? .env.production.enc

The .enc variants are committed to git. The .env* plain files are gitignored. Decrypt with sops -d .env.enc > .env before local dev.

Loading chain

Vite (Astro / SvelteKit / etc.) and Next.js both implement dotenv-chain:

  1. .env loaded first
  2. .env.<NODE_ENV> loaded second (overrides keys from step 1)
  3. .env.local loaded third (gitignored, per-developer overrides — optional)

No app code changes needed; Vite/Astro/Next do this automatically.

Sync to org secrets

sync-env-to-org-secrets.yml workflow pushes ALL THREE to org level with a naming convention so apps can key-resolve at CI time:

Source fileOrg secret name
.env (shared key FOO)FOO
.env.development key FOOFOO (same — development is the default)
.env.production key FOOFOO_PROD (production override)

CI workflows reading secrets at build time pick the right one based on deployment target:

env:
  RAZORPAY_KEY_ID: ${{ github.ref == 'refs/heads/main' && secrets.RAZORPAY_KEY_ID_PROD || secrets.RAZORPAY_KEY_ID }}

Why the split

Cross-refs


Edit on GitHub · Back to index