Scalingo Review Apps: Automated PR Previews Without the Operational Overhead
Scalingo Review Apps spin up isolated preview environments for every pull request — complete with fresh databases, inherited config, and automatic cleanup. Here's how to configure them via scalingo.yml, handle secrets safely, and avoid the common pitfalls that catch teams off guard.
13 Aug 2025, 21:45 UTC

The Problem: Staging Environments That Don't Match Production
Most teams run a single shared staging environment. It drifts from production — different data, stale dependencies, configuration gaps — and becomes a bottleneck when multiple developers need to validate changes simultaneously. Scalingo's Review Apps feature addresses this by spinning up a fresh, isolated environment for every pull request, automatically torn down when the PR closes.
How Review Apps Work Under the Hood
When you enable Review Apps in the Scalingo dashboard (or via scalingo.yml), the platform listens for webhook events from GitHub, GitLab, or Bitbucket. On pull_request open or synchronize:
- Scalingo clones the PR branch and runs the same buildpack detection as the parent app.
- It provisions a new app named
pr-<number>-<parent-app>on the same stack and region. - Add-ons (PostgreSQL, MySQL, MongoDB, Redis, etc.) are created fresh for that review app.
- Environment variables are inherited from the parent, then overridden by any
envkeys inscalingo.yml. - The app gets a unique subdomain:
https://pr-42-myapp.scalingo.io. - On PR merge or close, the app and its add-ons are destroyed automatically.
By default, inactive review apps are cleaned up after 7 days; this timeout is configurable in the dashboard.
Configuration in scalingo.yml
The scalingo.yml file at the repository root controls Review App behavior. It's version-controlled, so changes are reviewed alongside code. A minimal example for a Node.js service with a PostgreSQL database:
# scalingo.yml
reviewapps: true
formation:
web: 1 # single web container for previews
worker: 0 # disable background workers to save resources
addons:
- postgresql:starter-512
- redis:starter-256
env:
NODE_ENV: review
# Override sensitive keys so production secrets never leak
STRIPE_SECRET_KEY: sk_test_review_placeholder
SENTRY_DSN: ""
hooks:
postdeploy: "npm run migrate && npm run seed:fixtures"
Key points:
formationlets you run a smaller footprint than production (e.g., no workers, single web dyno).addonsprovisions fresh instances per review app — each PR gets its own database.envoverrides are critical: they prevent production API keys, webhook URLs, or database credentials from appearing in preview environments.postdeployhook runs after each successful deploy (including syncs on new commits). Use it for migrations and seeding.
Worked Example: Adding Review Apps to a Monorepo
Suppose you have a repository with frontend/ (React) and backend/ (Node/Express) directories. You want independent preview apps for each service.
- Create
frontend/scalingo.yml:app: myapp-frontend reviewapps: true formation: web: 1 addons: [] env: REACT_APP_API_URL: "https://pr-{{PR_NUMBER}}-myapp-backend.scalingo.io" hooks: postdeploy: "npm run build" - Create
backend/scalingo.yml:app: myapp-backend reviewapps: true formation: web: 1 worker: 0 addons: - postgresql:starter-512 env: NODE_ENV: review DATABASE_URL: "{{DATABASE_URL}}" # injected by addon hooks: postdeploy: "npm run migrate && npm run seed:fixtures" - Push to GitHub, open a PR. Scalingo creates two review apps:
pr-42-myapp-frontendandpr-42-myapp-backend. - Verify in the PR checks: both preview URLs appear, and the frontend's
REACT_APP_API_URLpoints to the backend review app via the templated{{PR_NUMBER}}variable.
This setup gives each service its own lifecycle, database, and scaling — no shared staging contention.
Trade-offs and Limitations
| Concern | Impact | Mitigation |
|---|---|---|
| Resource consumption | Each review app counts against plan limits (containers, add-on storage, hours). Free/low-tier plans hit quotas fast with many concurrent PRs. | Use minimal formation (web:1, worker:0), starter add-ons, and enable the 7-day auto-cleanup. Monitor usage via scalingo review-apps --app <parent>. |
| Secret leakage | Parent app env vars are copied by default — production DB URLs, API keys, OAuth secrets end up in preview apps. | Always override sensitive keys in scalingo.yml env with test values or empty strings. Audit with scalingo env --app pr-42-myapp. |
| Cold-start latency | First build for a new PR takes 3–8 minutes (buildpack, deps, add-on provisioning). Subsequent syncs are faster due to layer caching. | Keep package.json / requirements.txt stable; use scalingo.yml cache directives if available. Accept the delay for first-time PRs. |
| No custom domains | Only pr-<num>-<parent>.scalingo.io is available. Breaks OAuth callbacks, CORS, or hardcoded domain checks. | Use wildcard OAuth redirect URIs (e.g., https://pr-*.myapp.scalingo.io/auth/callback) or feature-flag domain validation in review mode. |
| Migration timeouts | postdeploy runs within the 30-minute build limit. Long migrations can fail, leaving the review app in a broken state. | Keep migrations fast; seed minimal fixtures. For heavy data, consider a separate seeding job triggered after deploy via webhook. |
| Missing autoscaling/cron | Review apps don't inherit parent's autoscaling rules or scheduled jobs. | Redefine formation and cron in scalingo.yml if load testing or scheduled tasks are needed in previews. |
Practical Verification Checklist
Before rolling out to the team, run through these steps in a test repository:
- Create a minimal Node.js/Python/Go app with a
scalingo.ymlenablingreviewapps: true. - Push to GitHub, enable Review Apps in the Scalingo dashboard for the parent app.
- Open a PR. Confirm the preview URL appears in the PR checks and loads successfully.
- Inspect build logs: verify
postdeployhook runs, add-ons are provisioned, and overridden env vars are present. - Run
scalingo review-apps --app <parent-app>from the CLI (requiresscalingoCLI authenticated) to list active review apps. - Leave the PR open past the inactivity timeout (or set it to 1 day in dashboard) and confirm automatic destruction.
- For monorepos, add two
scalingo.ymlfiles in subdirectories with distinctapp:keys and verify two independent review apps spawn per PR.
When to Adopt — And When to Hold Off
Review Apps shine when:
- Your team opens multiple PRs daily and needs isolated validation.
- You want database parity (fresh schema + fixtures) per PR without manual scripting.
- Configuration lives in code (
scalingo.yml) so preview behavior is auditable.
Hold off if:
- You're on a free tier with strict container/add-on limits — costs can spike unexpectedly.
- Your OAuth providers or third-party integrations don't support wildcard subdomains.
- Migrations routinely exceed 30 minutes; you'd need a separate seeding pipeline.
Next Steps
Start with a single service. Add scalingo.yml with reviewapps: true, a minimal formation, and explicit env overrides for every secret. Open a PR, watch the first build, then iterate on the postdeploy hook until migrations and seeding complete reliably. Once that's stable, extend to other services in the monorepo. The configuration is the documentation — keep it in version control and treat changes like any other code review.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.