Olympus Docs
CookbookDeployment

Staging environment best practices

A production-mirror for testing changes

A staging environment that closely mirrors production is essential. Differences between staging and prod are where bugs hide.

What "mirrors prod" means

Same:

  • Software versions.
  • Configuration shape (different values for secrets, same keys).
  • Hosting topology.
  • TLS, CSP, rate limiting.
  • Email provider (sandbox mode).
  • Backup / monitoring setup.

Different:

  • Domain names (staging.your-app.com vs your-app.com).
  • DB content (synthetic users, not real).
  • Sometimes smaller resources (cheaper).

Provisioning

If using Terraform:

# envs/staging.tfvars
env = "staging"
domain = "staging.your-app.com"
host_type = "cax11"  # smaller than prod's cax21
terraform apply -var-file=envs/staging.tfvars

If using a script:

ENV=staging ./scripts/provision.sh

Synthetic data

Don't copy prod data to staging, that's a data breach risk. Instead, seed synthetic users:

# scripts/seed-staging.sh
for i in $(seq 1 100); do
  curl -X POST $KRATOS_ADMIN/admin/identities -d "{
    \"schema_id\": \"default\",
    \"traits\": { \"email\": \"staging-user-${i}@example.com\" },
    \"credentials\": { \"password\": { \"config\": { \"password\": \"StagingPass123!\" } } },
    \"state\": \"active\"
  }"
done

Or use Faker:

import { faker } from "@faker-js/faker";
for (let i = 0; i < 1000; i++) {
  const user = {
    traits: {
      email: faker.internet.email(),
      first_name: faker.person.firstName(),
      last_name: faker.person.lastName(),
    },
  };
  // create
}

Realistic patterns

Beyond user count, simulate behavior:

# Cron in staging: simulate logins
0 */1 * * * curl -X POST staging/login --data ...

5-10 logins per hour. Audit log accumulates. Tests realistic load.

Differences to mind

Email

Use sandbox / dev mode of your provider:

  • Postmark: sandbox API key (returns success, doesn't actually send).
  • SES: configuration set with no-op destination.
  • Mailpit (self-hosted): all email visible in web UI.

So staging emails don't accidentally hit real customers.

Stripe / payment

Test mode (sk_test_...). Test card numbers (4242 4242 4242 4242).

OIDC providers

Some providers (Google, Apple) require redirect URIs to be registered. Register staging URIs:

https://staging-ciam.your-app.com/self-service/methods/oidc/callback/google

Same OAuth client OR separate. Prefer separate to avoid prod risk.

Public-facing rate limits

Staging is hit harder per identity (testing). Loosen rate limits if needed:

# staging override
rate_limits:
  login: { events: 100, window: 1m }  # vs 10 in prod

When to refresh staging

After major changes:

  • Schema migration → re-seed.
  • New features → seed accounts using new features.

Don't keep cruft from past testing. Periodic teardown + reseed (monthly?).

Deploys to staging first

PR merges to main → deploy to staging → soak test (1h) → deploy to prod

If staging issues: revert PR. Prod never breaks.

Build into CI:

# .github/workflows/deploy.yml
on:
  push:
    branches: [main]
jobs:
  staging-deploy:
    runs-on: ubuntu-latest
    steps:
      - ssh staging "cd /opt/olympus && git pull && podman-compose up -d"
      - run: ./scripts/smoke-test.sh staging
  prod-deploy:
    needs: staging-deploy
    runs-on: ubuntu-latest
    steps:
      - run: sleep 3600  # soak
      - ssh prod "cd /opt/olympus && git pull && podman-compose up -d"
      - run: ./scripts/smoke-test.sh prod

Cost

Staging adds ~50-100% to your infra cost. Worth it.

To reduce:

  • Smaller instance.
  • Stop staging at night (cron).
  • Single host containing both staging and prod (NOT recommended, staging issues can affect prod).

Branching: dev → staging → prod

If you want a third tier:

  • Dev: local. Mock everything.
  • Staging: shared. Real Olympus stack, sandbox external services.
  • Prod: real.

For single-developer projects, dev + prod is fine. Multi-developer / multi-team: staging.

Access control

Staging may have less restricted access:

  • Developers can SSH freely.
  • Admin endpoints reachable from office IP.

But still:

  • Don't expose to internet without auth.
  • Use distinct credentials from prod (different DB password, different OAuth secrets).
  • If staging is breached, prod must remain safe.

Sample data privacy

If you do copy prod DB to staging for realism:

  • Hash / redact PII first.
UPDATE identities SET traits = jsonb_build_object(
  'email', md5(traits->>'email') || '@redacted.local',
  'first_name', 'Redacted',
  'last_name', 'User'
);

Better: synthetic-only. Avoids the question entirely.

"Production-like" testing

For testing things that need real third parties (real OAuth providers, real email, real SMS):

  • Use a dedicated "staging account" at each provider.
  • Costs add up (Twilio per SMS). Be disciplined about volume.

On this page