OperationsstagingenvironmentsDevOps

How to Set Up a Staging Environment That Matches Production

Your staging environment should mirror production exactly. Here's how to set it up, keep it in sync, and avoid the 'works in staging, breaks in production' problem.

R

RaidFrame Team

February 3, 2026 · 5 min read

TL;DR — A staging environment should be a smaller clone of production — same services, same database schema, same environment variables (with test keys), same deployment pipeline. On RaidFrame, create one with rf env create staging --from production. It clones everything in seconds.

Why staging matters

Without staging, you're testing in production. That means:

  • Bugs hit real users before you catch them
  • Database migrations break live data
  • Third-party integrations fire real charges
  • Rollbacks are your only safety net (and they don't undo data changes)

With staging, you catch these problems before they affect anyone.

What staging should mirror

AspectShould Match Production?
Services (types, count)Yes (same services, smaller scale)
Database schemaYes (exact match)
Database dataPartial (subset or masked)
Environment variablesYes (with test API keys)
Deployment pipelineYes (same Dockerfile, same build)
Scaling rulesNo (use minimum instances)
Custom domainsNo (use staging subdomain)
Third-party integrationsYes (test/sandbox mode)

Set up staging on RaidFrame

Create from production

rf env create staging --from production
Creating staging from production...
  Services: 3/3 cloned
  Databases: 2/2 branched (schema only, no data)
  Env vars: 14/14 copied
  Cron jobs: 2/2 copied
 
✓ Staging ready at https://staging-my-app.raidframe.app
 
⚠ Review environment variables — production API keys were copied.
  Update test keys: rf env set STRIPE_KEY=sk_test_xxx --env staging

Set test API keys

rf env set STRIPE_KEY=sk_test_xxx --env staging
rf env set RESEND_KEY=re_test_xxx --env staging
rf env set SENTRY_DSN=https://[email protected] --env staging

Seed staging data

# Option 1: Branch production database with PII masking
rf db branch main --name staging-data --mask-pii
rf db restore staging-db --from staging-data
 
# Option 2: Run seed scripts
rf exec web "npm run db:seed" --env staging
 
# Option 3: Import a curated test dataset
rf db import staging-db ./test-data.sql --env staging

Configure auto-deploy

deploy:
  production:
    branch: main
    auto: true
  staging:
    branch: develop
    auto: true

Now pushes to develop deploy to staging, pushes to main deploy to production.

Scaling staging down

Staging doesn't need production-level resources:

services:
  web:
    scaling:
      production:
        min: 4
        max: 40
      staging:
        min: 1
        max: 2
  worker:
    scaling:
      production:
        min: 2
        max: 10
      staging:
        min: 1
        max: 1

Try RaidFrame free

Deploy your first app in 60 seconds. No credit card required.

Start free

Keep staging in sync

The biggest staging failure: it drifts from production over time.

Database schema drift

Use the same migration pipeline for both environments:

# Migrations run on staging first
rf exec web "npx prisma migrate deploy" --env staging
 
# Then production
rf exec web "npx prisma migrate deploy" --env production

Or use environment promotion — deploy to staging, test, then promote the same build:

rf deployments promote staging --to production

Environment variable drift

Audit regularly:

rf env diff --env production --env staging
DIFFERENCES (production → staging)
──────────────────────────────────
  STRIPE_KEY:     sk_live_xxx → sk_test_xxx  (expected)
  NEW_FEATURE:    true → (not set)  ⚠ missing in staging
  OLD_KEY:        (not set) → old_value  ⚠ exists only in staging

Service drift

If you add a service to production, add it to staging too. On RaidFrame, rf env create staging --from production can be re-run to sync services without destroying existing data.

Preview environments (per-PR staging)

Beyond a shared staging environment, use preview environments for per-PR testing:

deploy:
  preview:
    pull_requests: true
    auto: true

Every PR gets its own isolated environment with a unique URL. Reviewers test the actual deployment, not just the code diff.

PR #42: "Add user profiles"
✓ Preview: https://pr-42-my-app.raidframe.app
✓ Database: branched from staging
✓ Tests: passing

FAQ

How often should I reset staging data?

Weekly or after major releases. Stale data leads to false confidence — tests pass on staging but fail on production because the data shapes are different.

Should staging have real production data?

Use a masked copy. Real data structure catches bugs that synthetic data misses, but PII must be removed. Use rf db branch main --mask-pii to clone with automatic masking.

How do I test webhooks on staging?

Use test/sandbox mode for payment providers (Stripe test mode). For services that don't have sandbox mode, use rf tunnel to expose a local endpoint, or configure the staging URL as the webhook target.

Should QA use staging or preview environments?

Both. Preview environments for feature-specific testing (does this PR work?). Staging for integration testing (does everything work together before production?).

How much does staging cost?

On RaidFrame with minimum scaling, a staging environment mirrors production for roughly 15-20% of the production cost. A $100/mo production setup has a ~$15-20/mo staging environment.

stagingenvironmentsDevOpstestingdeployment

Ship faster with RaidFrame

Auto-scaling compute, managed databases, global CDN, and zero-config CI/CD. Free tier included.