How to Manage SaaS Environment Variables Across Staging and Production | Deployxa

Managing environment variables across staging and production is a common SaaS headache. Here is the founder's guide to keeping them organized and secure.

← Back to Dispatch Articles
Engineering Log

How to Manage SaaS Environment Variables Across Staging and Production

Managing environment variables across staging and production is a common SaaS headache. Here is the founder's guide to keeping them organized and secure.

How to Manage SaaS Environment Variables Across Staging and Production

Key Facts

  • Direct answer: The direct answer is that managing environment variables across environments requires four practices: separation (each environment has its own variables), documentation (a .env.example file lists all required variables), validation (the app validates variables at startup), and secrets management (secrets are never in code or committed files).

  • Practice 1: Environment Separation: Each environment (development, staging, production) has its own set of environment variables.

  • Practice 2: Documentation (.env.example): A .env.example file (committed to the repository) documents all required environment variables.

  • Practice 3: Validation: Validate environment variables at startup, so the app fails fast if a variable is missing.

  • Practice 4: Secrets Management: Secrets (API keys, passwords, tokens) need special handling.

Environment variables are the configuration layer of your SaaS: database URLs, API keys, feature flags, and secrets. Managing them across multiple environments (development, staging, production) is a common source of bugs, security issues, and deployment failures. This article is the founder's guide to keeping environment variables organized, secure, and consistent across environments.

The direct answer is that managing environment variables across environments requires four practices: separation (each environment has its own variables), documentation (a .env.example file lists all required variables), validation (the app validates variables at startup), and secrets management (secrets are never in code or committed files). For more on environment variables, see our article on a founder's guide to environment variables, secrets, and least privilege.

Practice 1: Environment Separation

Each environment (development, staging, production) has its own set of environment variables:

  • Development. Variables are in a local .env file (gitignored). The database URL points to a local database. API keys are test keys (e.g., Stripe test key). Secrets are development-only (not production secrets).
  • Staging. Variables are set in the Deployxa dashboard for the staging app. The database URL points to a staging database (a copy of production data). API keys are test keys or sandbox keys. Secrets are staging-specific.
  • Production. Variables are set in the Deployxa dashboard for the production app. The database URL points to the production database. API keys are live keys (e.g., Stripe live key). Secrets are production secrets (strong, rotated regularly).

The key principle is: never share secrets across environments. The production database password should not be the same as the staging database password. If a staging secret is compromised, it should not give access to production data.

Practice 2: Documentation (.env.example)

A .env.example file (committed to the repository) documents all required environment variables:

# .env.example (commit this file)

# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname

# Authentication
JWT_SECRET=generate_with_openssl_rand_base64_32
NEXTAUTH_SECRET=generate_with_openssl_rand_base64_32

# Stripe
STRIPE_SECRET_KEY=sk_test_or_live_key
STRIPE_WEBHOOK_SECRET=whsec_xxx

# Email
RESEND_API_KEY=re_xxx

# App
NODE_ENV=production
PORT=3000
NEXT_PUBLIC_APP_URL=https://myapp.com

This file serves three purposes: (1) it documents what variables are needed, (2) it helps new team members set up their local environment, and (3) it helps the pre-flight scanner verify all variables are set before deployment.

Practice 3: Validation

Validate environment variables at startup, so the app fails fast if a variable is missing:

// lib/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
  RESEND_API_KEY: z.string().startsWith('re_'),
  NODE_ENV: z.enum(['development', 'staging', 'production']),
  PORT: z.string().default('3000'),
  NEXT_PUBLIC_APP_URL: z.string().url(),
});

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

if (!parsed.success) {
  console.error('Missing or invalid environment variables:');
  console.error(parsed.error.format());
  process.exit(1);
}

export const env = parsed.data;

This ensures the app does not start with missing or invalid variables, which prevents runtime errors (e.g., Cannot read property 'DATABASE_URL' of undefined).

Practice 4: Secrets Management

Secrets (API keys, passwords, tokens) need special handling:

  • Never commit secrets to Git. Add .env to .gitignore. If a secret has been committed, remove it from history and rotate it.
  • Never use `NEXT_PUBLIC_` for secrets. Variables prefixed with NEXT_PUBLIC_ (Next.js) are inlined into client-side JavaScript, which means anyone can see them.
  • Rotate secrets regularly. Rotate production secrets every 90 days. For more on rotation, see our article on building an AI agent that manages your secrets.
  • Use a secrets manager for large teams. For teams with many secrets, use a secrets manager (e.g., Doppler, AWS Secrets Manager) instead of plain environment variables.

For more on secrets management, see our article on the secrets management gap.

Common Pitfalls and Troubleshooting

The first pitfall is sharing secrets across environments. If the staging and production databases use the same password, a staging compromise gives access to production. The fix is to use different secrets for each environment.

The second pitfall is not validating variables. Without validation, a missing variable causes a runtime error (e.g., the app crashes when it tries to connect to the database). The fix is to validate at startup.

The third pitfall is build-time vs runtime variables. NEXT_PUBLIC_* variables are inlined at build time (they are baked into the JavaScript bundle). Changing them requires a rebuild. Server-side variables (without NEXT_PUBLIC_) are read at runtime and can be changed without a rebuild. The fix is to understand the difference and to use server-side variables for values that change between environments.

The fourth pitfall is not documenting variables. Without a .env.example file, new team members do not know which variables to set, which leads to missing variables and runtime errors. The fix is to maintain a .env.example file.

The fifth pitfall is not rotating secrets. Secrets that are not rotated are vulnerable to compromise. The fix is to rotate production secrets every 90 days.

Conclusion: Organize, Document, Validate, Protect

Managing environment variables across staging and production does not have to be a headache. By separating environments, documenting variables (.env.example), validating at startup, and managing secrets properly, you can keep your configuration organized, secure, and consistent. This prevents deployment failures, security incidents, and team confusion.

Ready to organize your environment variables? Create a .env.example file, add validation, and ensure each environment has its own secrets. For more, see a founder's guide to environment variables, secrets, and least privilege and the vibe coder's guide to environment variables. Explore our free developer tools to speed up your workflow.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now