Tracking checklist for standing PR Preview back up on Clever Cloud, replacing the Heroku deployment.
Configuration details are in DEPLOYMENT.md (added in #187) — this issue is the sequence, not the reference.
What changes
|
Before |
After |
| Host |
Heroku |
Clever Cloud (Node.js runtime) |
| GitHub App |
pr-preview |
unchanged — same App, same App ID, same installations |
| S3 buckets |
default + WHATWG |
unchanged — same buckets, same URLs, same fronting layer |
| Every rotatable secret |
Heroku-era |
rotated |
Only the host moves. The App is not re-registered and the buckets are not touched, so no repository owner has to reinstall anything and every preview URL already linked from an open PR keeps resolving. The single externally-visible change is the App's webhook URL.
One consequence of the Heroku deployment being gone rather than idle (#186): there is no rollback target. Nothing to point the webhook back at if the new host misbehaves. The mitigation is that the service is already down, so a failed deploy leaves it no worse off, and the Console's Deployments panel can redeploy an earlier commit.
Credentials
Every rotatable secret is being regenerated, not recovered. Nothing here depends on getting values back out of Heroku, which is the right call anyway given they may no longer be readable. Procedures are in DEPLOYMENT.md under Rotating the secrets and AWS S3.
Rotating now is close to free: the service is down, so there is no live traffic to break during the window where a new GITHUB_SECRET and the App settings page disagree. The same change costs rejected deliveries once it is serving.
Look up — not secrets, nothing to rotate:
Rotate:
If the WHATWG credentials turn out to be unobtainable, ALLOW_MULTIPLE_AWS_BUCKETS=no routes every repo through the default bucket and the WHATWG env vars are ignored (lib/models/pr.js:195-200). Previews still work; WHATWG's move off their own hostname to a plain S3 URL. A deliberate fallback, not a default.
Retire the old credentials — once the new host has served real traffic:
Provision
Deploy and verify
Cut over
Clear the backlog
PR Preview has been down since #186, so open PRs have missed their previews, unfortunately this is impossible to automate afaik, so won't happen.
After a week of real traffic
Tracking checklist for standing PR Preview back up on Clever Cloud, replacing the Heroku deployment.
Configuration details are in
DEPLOYMENT.md(added in #187) — this issue is the sequence, not the reference.What changes
pr-previewOnly the host moves. The App is not re-registered and the buckets are not touched, so no repository owner has to reinstall anything and every preview URL already linked from an open PR keeps resolving. The single externally-visible change is the App's webhook URL.
One consequence of the Heroku deployment being gone rather than idle (#186): there is no rollback target. Nothing to point the webhook back at if the new host misbehaves. The mitigation is that the service is already down, so a failed deploy leaves it no worse off, and the Console's Deployments panel can redeploy an earlier commit.
Credentials
Every rotatable secret is being regenerated, not recovered. Nothing here depends on getting values back out of Heroku, which is the right call anyway given they may no longer be readable. Procedures are in DEPLOYMENT.md under Rotating the secrets and AWS S3.
Rotating now is close to free: the service is down, so there is no live traffic to break during the window where a new
GITHUB_SECRETand the App settings page disagree. The same change costs rejected deliveries once it is serving.Look up — not secrets, nothing to rotate:
GITHUB_INTEGRATION_ID— top of the App settings page. Fixed for the life of the App; it is the one value that cannot be rotated.AWS_BUCKET_NAME— the bucket in the S3 consoleWHATWG_AWS_BUCKET_NAME— the hostname serving WHATWG previews, not a bucket name; visible in the preview URL of any recentwhatwgPR commentRotate:
GITHUB_INTEGRATION_KEY— generate a fresh private key under Private keys on the App settings pageGITHUB_SECRET— set a new webhook secret and put the identical value in the app env; these two must match or every delivery is rejectedGITHUB_TOKEN— issue a new personal access token from the operator's accountAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY— new access key pair for the same IAM userWHATWG_AWS_ACCESS_KEY_ID/WHATWG_AWS_SECRET_ACCESS_KEY— same, if that IAM user is in an account we control. This is the only item that may not be ours to rotate unilaterally; if the user lives in a WHATWG-owned account it needs their cooperation. Worth checking early, since it gates the WHATWG path entirely.If the WHATWG credentials turn out to be unobtainable,
ALLOW_MULTIPLE_AWS_BUCKETS=noroutes every repo through the default bucket and the WHATWG env vars are ignored (lib/models/pr.js:195-200). Previews still work; WHATWG's move off their own hostname to a plain S3 URL. A deliberate fallback, not a default.Retire the old credentials — once the new host has served real traffic:
Provision
mainin the Information panel — Clever Cloud defaults tomaster, which this repo does not have, so nothing would deployNODE_ENV=productionPORT=8080— the platform only routes there and the code defaults to5000CC_NODE_VERSION=22GITHUB_INTEGRATION_KEYsurvived as multi-line PEM (a flattened one fails at the first webhook, not at boot)pico. The app waits on remote build services, so its reduced CPU priority costs little; 256 MiB of memory is the open question. See Instance sizing and scaling in DEPLOYMENT.md.unzipis present on the image (the WHATWG path shells out to it)Deploy and verify
Deploy; confirm
Express server listening on port 8080 in production modein the Logs panelAdd the domain; confirm TLS is live —
curl -I https://<host>/returns 404 (noGETroute exists; what matters is the handshake and that the app answered)Smoke-test the webhook endpoint before pointing GitHub at it. Sign the payload — this is the only check that proves the
GITHUB_SECRETin the app's environment is the value you think it is, and it catches a mismatch before GitHub is involved:Expect 200 with an ISO timestamp body, e.g.
2026-09-06T20:46:59.743Z. Responses verified against the current code:NODE_ENV=production, secret matchesX-Hub-SignatureheaderNote that 404 is also what
GET /returns, so a 404 on its own does not distinguish "wrong secret" from "wrong host" — only the signed 200 is positive evidence.The 500 is a latent defect rather than a deliberate rejection:
@snyk/express-x-hubonly attachesreq.isXHubValidwhen anX-Hub-Signatureheader is present, solib/app.js:16calls an undefined function on unsigned requests and throws. Harmless — the payload is never processed — but a public URL will collect 500s and logged TypeErrors from ordinary internet scanning. Worth fixing separately; not a blocker for this migration.Cut over
https://<host>/github-hook.pr-preview.jsonand confirm the comment updateswhatwg-owned PR separately — it is the only path using the second bucket, and the only one downloading, unzipping and diffing full HTML spec builds on the instance. A missingunzip, an undersized flavor, or badWHATWG_AWS_*credentials surface here and nowhere else.docs/config.htmlfromhttps://pr-preview.herokuapp.com/configtohttps://<host>/configClear the backlog
PR Preview has been down since #186, so open PRs have missed their previews, unfortunately this is impossible to automate afaik, so won't happen.
Redeliver failed webhook deliveries from the App's Advanced tab where they are still retainedFor the rest, setSTARTUP_QUEUEand restart, or push an empty commit to the affected PRsAfter a week of real traffic
pico, an OOM kill drops the whole in-memory job queue rather than just the PR that overran, so a single missing preview can be the visible edge of a lost queue. Size up if they show.