Configure preview deploys so they run against separate data stores and never inherit production credentials or live records.

Separate database instances per preview

Create a fresh database for each preview branch instead of reusing a shared staging instance or pointing at production. In the CI job that builds the preview, generate a unique identifier from the branch name and pull request number, then provision a new Postgres or MySQL instance with that suffix. Store the connection string as a short-lived secret that expires after the preview is torn down.

If full provisioning is too slow, maintain a pool of empty template databases and clone them on demand. The clone step must copy only schema and seed data; never copy production rows. Confirm the clone succeeded by running a row-count query that returns zero for any user or transaction tables before the preview container starts.

Seed only synthetic or anonymized records

Load preview databases from a fixed set of synthetic fixtures or from a nightly anonymized dump that strips PII and replaces identifiers. Run the anonymization step in a separate job that never has access to the preview runtime credentials. Store the resulting dump in an object bucket with a lifecycle rule that deletes files older than seven days.

Add a startup check in the application that refuses to start if it detects production connection strings or live customer IDs. The check can look for a known production table row or a specific environment variable that only the production deployment receives. Fail the container loudly so the preview never serves traffic with real data.

Scope secrets and outbound calls

Inject preview-specific secrets through the CI system rather than copying the production secret store. Use branch-based naming so a preview for feature-x never receives the Stripe live key or the production webhook signing secret. If a downstream service must be called, point it at a sandbox account that mirrors only the minimum required configuration.

Block webhooks and callbacks from reaching production endpoints by rewriting destination URLs inside the preview application or by using a dedicated webhook relay that filters on preview identifiers. Log the destination of every outbound call during preview runs and alert if any URL contains a production domain.

Cleanup and audit trails

Destroy the preview database and its secrets when the pull request closes or after a fixed idle period. Wire the destroy step into the same pipeline that created the preview so that orphaned resources do not accumulate. Record the creation and deletion timestamps along with the branch name in an audit log that the team can query.

Run a nightly job that lists all preview databases and cross-checks them against open pull requests. Any database without a matching open PR is deleted and its secret references revoked. This catches cases where manual deploys or failed pipeline runs left resources behind.