Learn Build a Real SaaS with Claude Code Production Environment Management

Production Environment Management

Intermediate 🕐 18 min Lesson 20 of 25
What you'll learn
  • Explain why a single environment variable name can hold different values across Development, Preview, and Production
  • Use vercel env add and vercel env ls to set and audit variables scoped to a specific environment
  • Explain what marking a variable sensitive changes about its visibility and which environments support it
  • Use vercel pull and vercel env run to work with non-local credentials without writing them to a tracked .env file
  • Identify when a custom environment like staging would be useful beyond the three Vercel defaults
  • Explain why a DATABASE_URL environment mismatch is more dangerous in this app than a Stripe key mismatch, which fails loudly

Three Environments, Not One .env File

Lesson 19 got every variable from .env.example into Vercel once. That is enough for a first deploy, but it glosses over a real question: a Stripe test-mode key and a Stripe live-mode key are both valid STRIPE_SECRET_KEY values, and nothing stops you from accidentally pointing a preview deployment at your production Supabase project if you only ever set one value per variable name. Vercel's environment variable system exists specifically to prevent that -- every variable is scoped to one or more of three default environments, plus optional custom ones.

The Three Default Environments

Environment What triggers it
Development
Your local machine running next dev -- pulled in via the Vercel CLI, not deployed
Preview
Any deployment from a Git branch that is not the production branch -- e.g. opening a pull request
Production
A deployment from the production branch (usually main) -- the live, user-facing app

Vercel also supports custom environments -- a longer-running pre-production target like staging or qa with its own variable set, useful once "preview per pull request" stops being granular enough for how your team works. For this project, the three defaults are enough: Development for your machine, Preview for testing a Stripe webhook or Supabase auth change before it ships, Production for the real thing.

Setting Variables Per Environment

The Vercel dashboard's Settings -> Environment Variables panel lets you check which of Development, Preview, or Production a given value applies to when you create it -- the same variable name can have a different value per environment, which is exactly how a Stripe test key in Preview and a Stripe live key in Production coexist under the identical name STRIPE_SECRET_KEY without either one leaking into the other's deploys. The CLI equivalent makes the same scoping explicit:

# Set a variable for production only
vercel env add DATABASE_URL production

# Set a different value for preview deployments
vercel env add DATABASE_URL preview

# Audit what is actually configured per environment
vercel env ls production
vercel env ls preview
vercel env ls development

Running vercel env ls against each environment before a deploy is the cheap habit that catches a missing variable before it becomes a 500 in production -- a mismatch between what Preview has and what Production has is, per Vercel's own documentation, a common cause of "it works in preview but breaks in production."

Sensitive Variables

Secrets like STRIPE_SECRET_KEY and SUPABASE_SERVICE_ROLE_KEY can be marked sensitive when you add them, either in the dashboard or with a CLI flag:

vercel env add STRIPE_SECRET_KEY production --sensitive

A sensitive variable behaves identically at runtime, but its value becomes non-readable in the dashboard once saved -- nobody with project access can view it again, only overwrite it. That matters specifically for the service-role key and live Stripe secret, since either one bypasses Supabase row-level security or moves real money if it leaks. Sensitive variables are only available in Preview and Production, not Development, by design.

Pulling Variables Down Locally, Safely

When you need to debug something that only reproduces against staging or production data, do not hand-copy values out of the dashboard into your local .env -- pull them properly:

vercel pull --environment=production

This writes the values to a git-ignored .vercel/.env.production.local file rather than your tracked .env, and you can run a single command against those values without writing them to disk at all:

vercel env run -e production -- npm run build

That last pattern is worth using deliberately rather than out of habit -- running your own machine directly against production Stripe or Supabase credentials, even briefly, means any local mistake (a stray test script, a console.log of a secret, a debugger left attached) has production blast radius. Reach for it only when reproducing a bug genuinely requires production data, and prefer Preview-scoped credentials for anything exploratory.

Why This Matters More for This App Specifically

This project has three integrations where a Development/Preview/Production mismatch is not just inconvenient but actively dangerous: a Stripe webhook signed with the wrong environment's secret fails closed (the signature check in app/api/billing/webhook/route.ts rejects it, so at least it fails loudly), but a DATABASE_URL pointed at the wrong Supabase project silently writes test data into what you believe is a clean production database, or vice versa. Treat vercel env ls as part of your pre-deploy checklist the same way you would treat running the test suite -- it costs one command and catches a class of mistake that is otherwise invisible until a customer hits it.

With deployment and environment scoping handled, the dashboard is live and safely configured -- but live also means you can no longer watch it run in a terminal. The next lesson covers what visibility actually looks like once the only way to know something broke is a monitoring tool telling you.

Key takeaways
  • Vercel scopes every environment variable to Development, Preview, Production, or a custom environment -- the same name can hold a test key in Preview and a live key in Production safely
  • vercel env ls per environment before a deploy is a cheap way to catch a missing or mismatched variable before it causes a production failure
  • Sensitive variables (vercel env add NAME production --sensitive) become unreadable in the dashboard after creation and are only available in Preview and Production, never Development
  • vercel pull --environment=production writes credentials to a git-ignored .vercel/.env.production.local file rather than your tracked .env
  • vercel env run -e production -- <command> runs a single command against production credentials without writing them to disk -- safer than pulling and forgetting to clean up
  • A wrong DATABASE_URL fails silently (writes to the wrong database); a wrong Stripe webhook secret fails loudly (signature check rejects it) -- the silent failure mode is the more dangerous one to guard against