ADR-0006 — A full preview stack on every pull request
| Date | 2026-08-17 |
| Superseded by | — |
Context
The stated requirement:
When things are pushed or created as a PR, I want to deploy all these and add them to the PR so that I can test them from my phone really easily without having to do local connections and all that stuff.
The thing being avoided is specific and familiar: to try a branch on a real phone you normally start Metro, find your laptop's LAN address, make sure the phone is on the same network, point the app at a local API, and discover the tunnel has died. It is five minutes of friction on every iteration, and the result is that changes get tested in a simulator instead of on a phone — which for an app used outdoors in bright sun with cold hands is the wrong place.
Decision
Every pull request deploys a complete, isolated stack and posts the links back as a sticky comment.
| Component | How | Isolation |
|---|---|---|
| API | wrangler versions upload --env preview | Its own version preview URL; production traffic untouched |
| Database | wrangler d1 migrations apply --env preview --remote | Shared preview D1, migrated |
| iOS app | eas update --branch pr-<n> | Its own EAS branch |
| Docs | wrangler pages deploy --branch pr-<n> | Its own Pages preview URL |
The detail that makes it work:
The app update has the PR's API URL baked into it. EXPO_PUBLIC_API_URL is set from the
Worker preview URL captured earlier in the same job, so scanning the QR code gives you a phone
already pointed at the backend built from the same commit. No local setup, no editing a URL.
Two more deliberate choices:
versions upload, notdeploy. It publishes a version and returns a preview URL without shifting any production traffic. There is no way for a preview to become production by accident.- The workflow polls
/healthand asserts the returnedrevisionmatches the PR head SHA before commenting. A green comment therefore means the preview is genuinely up and serving the right commit, not that a deploy command exited zero. - Forked PRs get CI only. They cannot read secrets, and deploying them would let anyone with a fork write to the preview database.
Consequences
What gets harder:
- The preview database is shared across PRs. Two branches with conflicting migrations will
fight. Per-PR D1 databases are possible (
wrangler d1 createper PR) and were rejected for now as more moving parts than the current volume of work justifies. If it becomes a problem, that is the fix. - Migrations run against a real database before review. A destructive migration on a preview branch damages the preview data. Preview data is disposable, but the seed script has to be re-runnable — it is.
- Secrets are needed for the pipeline to work at all.
CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID,EXPO_TOKEN, plus theEAS_PROJECT_IDvariable. Until those exist, the preview workflow fails on every PR. Listed in Environments. - Third-party actions —
expo/expo-github-actionandmarocchino/sticky-pull-request-comment— are in the supply chain of a workflow that holds deploy credentials. They are pinned by major tag; pinning to a SHA would be stricter.
What we are committed to:
- Cloudflare and EAS as the deploy path. The workflow is written around
wranglerandeasspecifically. - Keeping
/healthhonest, because the pipeline asserts on it.
What this creates:
- A development build has to exist on the phone before any of this is useful. Expo Go cannot load EAS Updates. This was not obvious when the decision was made and is the single biggest prerequisite: a build signed with a free Apple ID lasts seven days, so the working pattern is a couple of minutes at the Mac each week, after which every PR is a scan from anywhere. Paying for the Apple Developer Program (£79/yr) removes the weekly rebuild and allows cloud builds; it is not needed to review a PR. Details in CI/CD.
- Every PR costs a Worker version, an EAS update, and a Pages deployment. All cheap; not free.
- A rule worth stating: do not merge a PR whose preview never came up. A broken preview means either the change or the pipeline is broken, and both are worth knowing before merge.
Alternatives considered
Expo Go with a tunnel, run locally
The default workflow. Rejected — it is exactly the friction the requirement names. It also cannot be shared: nobody else can try your branch.
TestFlight builds per PR
Real builds, real distribution. Rejected on cycle time. Even with internal testing, a build and upload is minutes-to-tens-of-minutes against seconds for an EAS Update, and it burns build credits. Full builds still matter for release candidates and for anything that changes native code — an EAS Update cannot deliver a new native module.
API preview only, app pointed at it manually
Half the work. Rejected because the manual half is the annoying half. Typing a workers.dev URL
into a settings screen on a phone, per PR, is precisely the friction being removed.
A single shared staging environment
Simpler, one URL. Rejected because branches then queue behind each other, and "is staging currently running my branch?" becomes a question people have to ask.
Open questions
- Per-PR databases. Worth doing when two PRs with schema changes are open often enough to collide.
- Cleanup. EAS branches for merged PRs are not deleted. Neither are Worker versions. Both accumulate.
- Native builds. An EAS Update cannot deliver a native module change. There is no workflow yet for "this PR needs a new dev build" — currently it has to be said in the PR description.
- Pinning actions to SHAs rather than major tags.
Related
- CI/CD and previews — how the workflows actually work
- Environments — the secrets required
- ADR-0003 — why EAS Update is available to us at all