You ask for a script that deletes inactive accounts, and you test it. The script is correct. The accounts it deletes belong to real customers, because the connection string it read was the production one.
This is the most expensive beginner mistake in the whole section, and it is prevented by one habit rather than by any tool.
The idea
An environment is one running copy of your app together with the data and settings it points at. The code is the same in all of them. What differs is which database it talks to, which API keys it holds, and who is allowed to see it. Your machine is one environment. The live site your users visit is The environment real users are actually using. Its data is real, so mistakes there are permanent.. A private copy of production, with fake data, used to check a change before real users get it. is an optional third one in between.
LOCAL STAGING PRODUCTION
(your laptop) (private URL) (myapp.com)
------------ ------------ ------------
fake data fake data REAL USER DATA
test API keys test API keys live API keys
audience: you audience: you audience: everyone
break it freely break it freely breaking it is an incident
| | |
+---- same code ------+------- same code -----+
the only irreversible box is the one on the rightHow it works
- The separation lives in configuration, not in code. One codebase reads
DATABASE_URLfrom its environment and gets a throwaway database on your laptop, another throwaway one in staging, and the real one in production. The application never knows which it is. - Most hosts give you preview environments for free. Vercel builds every branch you push at its own URL, so "does this branch work" gets answered before you merge. Netlify does the same for pull requests, and its branch deploys have to be switched on first. For a solo builder this is usually the honest answer to staging.
- Databases are the part you have to separate deliberately. Nothing stops your local app from
connecting to the production database, and pointing a local
.envat production is the single most common way this goes wrong. - Third-party services have their own split. Stripe issues test keys (
sk_test_) alongside live ones (sk_live_), and Resend gives you addresses like[email protected]that accept a message without sending it to a person. Using the live Stripe key locally means a test checkout charges a real card.
What to do
- Give local development its own database from day one. The short path is a second free
Supabase project called
myapp-dev: create it, apply your migrations to it, and put its connection string in your local env file, which secrets and environment variables covers. Running Postgres locally under Docker is the other option and it is a bigger detour than one sentence makes it sound, since it means installing Docker and learning it first. - Say it out loud when you brief your assistant. "This runs against the local dev database. Do
not use the production connection string." Then check what it did before you run anything with
DELETE,DROPorUPDATEin it. - Add staging only when you have someone to protect. With no users, your local machine is your staging. Once real people depend on the app, or once you have a paid tier, a staging environment stops being ceremony.
- Seed staging with fake data, not with a copy of production. Copying the production database into staging is a common shortcut and it moves real names, emails and addresses into a system with weaker protection, where an exposure would be a reportable breach of data you were trusted with. Treat it as off limits. Write a seed script that generates rows instead, which your assistant can do from your schema in a few minutes.
Where it breaks
Three environments cost three sets of everything: three databases, three sets of keys, three things that drift. A solo builder with no users who insists on full staging usually ends up with a staging copy that has been broken for a month and tells them nothing. Skipping it is a legitimate choice as long as you know you are trading it for care at the moment of deploy.
Staging also lies in a specific way. It has small, clean, fake data, so a query that takes eleven minutes against a real table finishes instantly there, and a bug that only appears with a real volume of records will not show up until production. A green staging run raises your confidence without ever finishing the argument.