Platform
Staging that does not look like production is a costume
ZenHorizon Engineering · 22 January 2026 · 4 min
January is when last year’s “we will fix staging” tickets get a new due date. The environment still has three users named Test Test, an empty search index, and a payment provider in a mode that never returns the error your production account returns on the second retry.
Then you ship. Production has a tenant with 1.2 million rows, a locale you forgot, and a webhook that arrives twice. Staging did not mention any of that. It was not supposed to. It was a costume.
We are not asking you to clone production spend. We are asking you to stop using an environment that can only pass the happy path as a gate for a system that does not live there.
What “like production” actually means
Not the same invoice. Not the same customer list sitting in plaintext on a laptop. It means the same shape.
- Data volume in the same order of magnitude on the hot tables, even if you subset the long tail.
- The same auth path. If production is SSO, staging is SSO. A local password bypass will hide the bug that blocks Monday’s login.
- The same async edges: the queue, the mail sender, the webhook verifier. Mocking all of them is how you discover production.
- The same migration path. If you only run migrations forward on prod because staging was rebuilt from a dump, you will learn about locks the hard way.
- The same feature flags, with names that match. A flag that exists only in prod is a second product.
Order of magnitude is the phrase people skip. Ten rows will not show you the sequential scan. A million anonymised rows of the wrong shape will not either. You want the painful tenant: the one with the weird locale, the one who uploaded 40,000 SKUs, the one whose webhook endpoint is slow on purpose.
Subset, do not invent
Synthetic data is fine for unit tests. It is a poor staging strategy for anything with real joins. We take a scrubbed subset: one or two tenants that look like the painful ones, PII hashed or replaced, files swapped for placeholders, then we restore that snapshot on a schedule so the environment cannot rot into a playground.
Scrubbing is a product. Write down what leaves the building. Phone numbers, emails, government ids, health notes, card tokens. If you cannot say whether Aadhaar or PAN is tokenised, do not copy the dump onto a laptop, even “just this once”. India is not a special case here. It is the case we work in, and the penalty for being casual is not theoretical.
The restore job is the environment. If a human has to remember three CLIs and a VPN dance, staging will be stale by Thursday. One command. One nightly refresh. A Slack line when it fails. That is the bar.
The search index and the cron
Two things are always stale. Search, because nobody rebuilt it after the last restore. And scheduled jobs, because someone disabled them after a staging email went to a real customer in 2023 and the fear never left.
Give staging a mail catcher. Mailpit, a provider sandbox, we do not care. Give it a reindex command the newest engineer can run. Put both in the README that people actually use — the one in the repo root, not the Confluence page from the last vendor.
Webhooks need a sink you control. If Stripe or Razorpay cannot reach staging, you do not have payment tests. You have a UI that pretends a 200 happened. ngrok links in a wiki are how secrets leak. Use a durable tunnel or a provider CLI that your CI understands.
Who is allowed to click around
Staging with production-shaped data is still production-shaped risk. SSO on staging. No shared password in a 1Password note titled “staging admin”. Break-glass in a vault with an expiry. If a contractor needs access, they get a time-boxed role, not a dump.
We treat a staging leak like a production leak. Because legally, if the scrub missed a field, it is. The runbook is the same: rotate, notify, write down what the subset job failed to catch, fix the job before the next restore.
If you cannot staff that runbook, your subset is too real for your process. Shrink the data until the process fits. A smaller honest subset beats a full dump you are afraid to admit you have.
A smaller prod is still cheaper than a surprise
We would rather run a small, production-shaped staging eight hours a day than a 24/7 clone that nobody trusts. Scale to zero in the evening. Keep the data. The bill stays quiet. The incident rate after deploys stays quieter.
Preview apps per pull request are excellent for UI. They are a poor substitute for staging if they spin up empty. Use them in addition, with seed data, not as the only place a migration runs.
If your staging cannot reproduce the last Sev-2, it is not an environment. It is a preview of the happy path. Customers do not live there.