Introduction
When you ship an AI-generated web app, the code is only half the battle. The other half is everything the code talks to — API keys for inference, database connection strings, third-party service credentials, and feature flags. Mishandling these in production is the single most common cause of real outages and real security incidents.
This lesson covers the practical workflow for keeping secrets safe across three environments: local development, staging, and production. You will learn a single mental model that works whether you deploy on Vercel, Railway, Render, Fly.io, or a raw VPS.
The Three Environments
Every app you build lives in at least three places, and each place has different rules for secrets.
Local development happens on your laptop. You need fast iteration, so secrets live in a `.env` file that is git-ignored. You load them with a library like `dotenv`. This file is never committed and never leaves your machine.
Staging is a copy of production with fake data. It exists to catch bugs before users see them. Secrets are injected by the hosting service (Railway Variables, Vercel Environment Variables, etc.) and are scoped to that environment only.
Production is what real users hit. Secrets come from the same hosting service but live in a stricter namespace, often with separate access controls. A leaked staging key is annoying; a leaked production key is a billing disaster.
Loading Environment Variables in Code
The pattern below works for almost every Node.js or Python backend. The key idea: read once at startup, fail loudly if a required secret is missing.
Notice the startup check. It is far better to crash at boot than to silently send `undefined` to your database client at 3am. The `dotenv` call only reads a file if it exists, which means the same code runs unchanged in production where the host injects variables directly.
For Python, the equivalent uses `pydantic-settings`:
Pydantic will raise a clear validation error if any required field is missing, achieving the same fail-fast behavior.
Database Connection Patterns
For a Postgres database on a managed service like Neon, Supabase, or Railway, the connection string is your secret. Treat it like a password.
Two things matter here. First, connection pooling prevents your app from opening a new TCP connection on every request. Second, SSL is required in production. Many managed databases reject unencrypted connections, but being explicit protects you if the default changes.
For serverless platforms (Vercel Functions, Cloudflare Workers), use a connection pooler that lives outside the function — Neon has one built in, Supabase has the `pgbouncer` transaction mode. The pool object above lives in process memory, which on serverless means a fresh pool per cold start, which is slow and can exhaust database connections under traffic spikes.
Managing API Keys for AI Services
AI APIs (OpenAI, Anthropic, Google, etc.) bill per token. A leaked key can drain a credit card in hours. Three rules keep you safe.
First, set a hard spend limit in the provider dashboard. Every major provider offers this. Second, create separate keys per environment — `myapp-dev`, `myapp-staging`, `myapp-prod` — so you can revoke one without touching the others. Third, never log the key, and never include it in error messages that get sent to a logging service.
The error handler captures the response body for debugging, which is fine because it does not contain the key — only the upstream provider's error. If you ever need to log the request headers for debugging, redact the `Authorization` field first.
Common Mistakes and How to Avoid Them
The most frequent mistake is committing a `.env` file. Even once. Git history is forever, and bots scan GitHub for exposed keys within minutes of a push. Add `.env` to `.gitignore` on day one, and use tools like `git-secrets` or `gitleaks` in CI to block accidental commits.
The second mistake is using the same database for development and production. It feels convenient until a `DROP TABLE` in a local script wipes production. Always use separate databases, and ideally separate provider projects, for each environment.
The third mistake is rotating keys without a plan. When you rotate a database password, every running instance of your app will fail to reconnect until it restarts. On platforms that support it, use connection strings that include the password and design your app to retry on startup. On platforms like Vercel, a redeploy after rotating the secret handles this automatically.
Summary
Treat every secret as if it will leak tomorrow — because eventually it will. Use `.env` files locally with `dotenv` or `pydantic-settings`, let your hosting platform inject variables in staging and production, validate required secrets at startup, pool database connections with `ssl: true` in production, and give every AI API key a spend limit plus a unique key per environment. This setup takes one afternoon to wire up and saves you from the most common category of production incident.
Lesson Checkpoint
Review what you learned and get feedback on your work.