2025-10-27 | PreviewProof Team

The Preview-Friendly Stack: A Checklist for Making Your App Ephemeral-Environment Ready

preview environmentsarchitectureephemeral environmentstwelve-factordeployment

Most teams that try preview environments don’t fail because the tool is bad. They fail because their application was never designed to be deployed anywhere except its one production environment and a developer laptop. The preview tool spins up a copy, and everything hardcoded, baked-in, or implicit about “where this app runs” starts breaking.

Preview-friendliness is an architectural property, not a feature you turn on. Here’s a checklist worth running before you spend money on preview tooling — most of these fixes pay for themselves regardless of which tool (or homegrown setup) you eventually pick.

Configuration and URLs

The first thing that breaks in a preview environment is anything that assumed the app’s own URL.

Nothing in your codebase should hardcode the production hostname. Not in config files, templates, email senders, or <link rel="canonical"> tags. The app’s external URL must be injected at runtime as APP_URL or equivalent. Search your repo for your production domain — every match is a future bug.

Internal service URLs must be configurable per environment. If your frontend talks to api.example.com because the string lives in a Next.js config, your preview frontend will talk to production every time. Use env vars and resolve at build or boot time. Build-time injection (Next.js NEXT_PUBLIC_*, Vite VITE_*) has its own implications for environment variables in ephemeral previews.

Generated URLs in emails and webhooks must use the runtime base URL. A welcome email linking to app.example.com/verify?token=... from a preview sends the user to production. Either disable outbound mail in previews or compute the base URL at send time.

Data layer

Database connection comes from env. Obvious, but check anyway — Rails database.yml, Django settings.py, and Spring application.yml all accumulate per-environment branches that bake hostnames in.

Migrations run non-interactively at boot. The container entrypoint runs prisma migrate deploy, alembic upgrade head, rails db:migrate — whatever your stack uses, idempotently. See migration patterns that don’t break preview environments for the per-framework gotchas.

You have a deterministic seed strategy. Empty databases produce useless previews. See how to seed Postgres for ephemeral previews for the four main strategies.

No production data, ever. Even sanitized snapshots are a liability. Handling PII in preview seed data covers why.

Decide upfront: per-preview database, or shared. Per-preview vs. shared dev database walks through the trade-offs.

Identity and authentication

OAuth callback URLs are the second-most-common source of preview-environment pain.

OAuth clients must accept dynamic callback URLs. A Google or GitHub OAuth app with a single registered redirect URI can’t serve previews. You need wildcard-tolerant providers, dynamically registered apps per preview, or an auth proxy on a stable domain. OAuth callback URLs for ephemeral previews covers the patterns.

Session cookies must work on the preview’s domain. Cookie Domain set to .example.com won’t work on pr-42.preview.example.com if your preview lives on a different parent domain.

Service-to-service auth must not assume fixed hostnames. See service-to-service auth in multi-service previews.

Integrations and webhooks

Outbound email goes through a sandbox. Mailtrap, SES sandbox, or smtp://blackhole. Previews fail closed on real mail.

Outbound payments and SMS use test mode. Stripe test keys, Twilio test credentials. Stripe, Twilio, SendGrid test mode in previews covers the patterns.

Inbound webhooks point at the preview’s URL. Either register dynamically per preview, use a router like Svix or Hookdeck, or accept that some integrations only test in staging. See webhook handling for preview URL changes.

Feature flags scope correctly. Default-on production flags will leak into previews. Use a separate flag environment per preview, or scope evaluation by context. Feature flags in preview environments.

Background work

Job runners can scale to zero or be skipped. A Sidekiq, Celery, or BullMQ worker for every PR burns money fast. Background jobs in ephemeral previews goes deep.

Scheduled jobs are off by default. A nightly billing job in 30 previews concurrently is a bad time. Gate cron with if ENV['ENABLE_CRON'].

Build and runtime

One image, many environments. Configurable entirely via env vars and mounted secrets. If you have to rebuild to deploy to a different environment, your preview pipeline will be slow and expensive.

Health checks that mean something. A /healthz that checks the database, queue, and any critical dependency. Otherwise a preview tool routes traffic to a half-booted process.

Boot time under sixty seconds. Reviewers won’t wait. Profile your boot path. Usual suspects: slow migrations, slow seeds, synchronous warm-up that should be lazy.

Observability and cost

Logs go to stdout. Not files, not sidecars with special config, not log shippers that can’t handle ephemeral hostnames. Twelve-factor was right.

You can spot zombie environments. Previews that should have torn down but didn’t are the dominant cost driver. Cost-aware preview environments.

Repo conventions

Your repo is preview-aware in its docs. A CLAUDE.md or AGENTS.md that tells coding agents how the preview pipeline expects them to behave saves real time. The CLAUDE.md guide for preview-aware repositories.

You can audit for preview-readiness in an afternoon. Making a repo preview-ready: an audit.

How to use this list

Don’t try to fix everything at once. Run through it, pick the three things most likely to bite you on day one. For most teams that’s hardcoded URLs, OAuth callbacks, and outbound email. Fix those three and the rest comes incrementally.

If you’d rather not build the preview pipeline that consumes this preview-friendly app — provisioning, lifecycle, sign-off, evidence — that’s the part PreviewProof does. We don’t fix your hardcoded hostnames (nobody can), but we run the previews on top of an app that’s been made preview-friendly, with the review and verification workflow attached. Get the architecture right first, then bring the tooling.