Public: anyone can read this; the crew can change it.
Platform app move gap: Custom domain for an app, with certificates and a one-step revert #11712
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/11700-platform-app-move-gap-custom-domain-for → main 3 commits fork 2 threads · 2 unresolved
3 files
changed +47
−3
docs/README.md +1
| @@ -34,6 +34,7 @@ platform for organizations. | ||
| 34 | 34 | - [](OPERATING.md): configuration, health, releases, switches and limits. |
| 35 | 35 | - [](METRICS.md): the Prometheus endpoint. |
| 36 | 36 | - [](DOMAINS.md): the ways the forge stops and what it reports. |
| 37 | - [](features/app-own-domain.md): cut-over, certificate, and the way back. | |
| 37 | 38 | - [](SNAPSHOT.md): nightly copies and what they hold. |
| 38 | 39 | - [](RECOVERY.md): a printable guide for a forge in trouble. |
| 39 | 40 | - [](RESTORE.md): bring a forge back on a fresh machine. |
docs/features/app-own-domain.md added +43
| @@ -0,0 +1,43 @@ | ||
| 1 | # An app's own domain: cut-over and the way back | |
| 2 | ||
| 3 | The step of a move that can strand a | |
| 4 | shop (`platform-app-move.md`). The forge serves a verified name | |
| 5 | and its proxy fetches the certificate; it never writes DNS, so the revert is | |
| 6 | the owner's records, put back by hand in one edit. In order: | |
| 7 | ||
| 8 | 1. **Write the old records down first.** Every record at the name (A, AAAA, | |
| 9 | CNAME, ALIAS, and the TTL of each) as the old host serves them today, | |
| 10 | in the migration issue. This list is the way back; the forge cannot read | |
| 11 | it for you after the cut. | |
| 12 | 2. **Add the name** (Settings → App → Domains, `app_domains` `add`, or | |
| 13 | `POST …/app/domains`). The answer lists the TXT proof and the CNAME or | |
| 14 | ALIAS to publish. Publish the **TXT only**; the old A/CNAME stays. | |
| 15 | 3. **Check** (`check`). The sentence says verified, or what is missing. The | |
| 16 | old site still answers: nothing has moved. `GET /__forge/domains/ask?domain=<host>` | |
| 17 | answering 200 is what lets the proxy issue the certificate on the first | |
| 18 | request (docs/APPS.md, _Your own domain_). | |
| 19 | 4. **Cut:** replace the old A/CNAME with the CNAME/ALIAS the forge gave. The | |
| 20 | first request to the new name pays for issuing the certificate (a second | |
| 21 | or two); request it once yourself, over https, before telling anyone. | |
| 22 | 5. **Revert, one step:** put the step-1 records back. Traffic returns to the old | |
| 23 | host as each resolver's cache of the new record expires, so keep the TTL | |
| 24 | low (`platform-app-move.md`, §6) until the shop is settled. | |
| 25 | The forge keeps answering at the name until the domain is removed | |
| 26 | (`remove`); removing it is only tidiness once DNS no longer points here, | |
| 27 | and the old host's certificate was never touched. | |
| 28 | ||
| 29 | Left out, on purpose: the forge does not edit DNS at a registrar (#11717), | |
| 30 | show the certificate's expiry (#11718), or store the old records for you | |
| 31 | (#11719). This closes #11700 as a runbook over a feature that already ships, | |
| 32 | not as new code. | |
| 33 | ||
| 34 | ## What keeps these claims true | |
| 35 | ||
| 36 | All in `test/app-domains.test.ts`: | |
| 37 | - the ask endpoint answers 200 only for a verified host (also with case and a | |
| 38 | trailing dot) and 404 for an unverified, unknown or missing name: "verified, the forge serves the | |
| 39 | app at the host and every URL follows"; | |
| 40 | - before verification the forge answers for itself and the ask says 404: | |
| 41 | "before verification the forge answers for itself at the host"; | |
| 42 | - removal makes the host unknown again and the ask 404: "removal makes the | |
| 43 | host unknown again". |
docs/features/platform-app-move.md +3 −3
| @@ -43,16 +43,16 @@ counts. The old copy stays until that has passed. | ||
| 43 | 43 | ## 6. Cut-over (person, quiet hour, the owner says "go") |
| 44 | 44 | 1. Lower the DNS TTL a day ahead. |
| 45 | 45 | 2. Put the old app read-only; take a last dump; load; compare counts. |
| 46 | 3. Point the domain at the forge; switch payment webhooks and live keys. | |
| 46 | 3. Point the domain at the forge (`app-own-domain.md` has the steps, the | |
| 47 | certificate and the revert); switch payment webhooks and live keys. | |
| 47 | 48 | 4. Place one live order. |
| 48 | 5. **Way back:** the old DNS records, written down before step 3; restoring | |
| 49 | 5. **Way back:** the old DNS records, written down before step 3 (`app-own-domain.md`); restoring | |
| 49 | 50 | them is the one step. The old app stays up read-only for a week. |
| 50 | 51 | |
| 51 | 52 | ## What the forge lacks (each is an issue) |
| 52 | 53 | - A database import for an app (`migrate` has no data path): upload a dump, load it, report counts and checksums. (#11697) |
| 53 | 54 | - A Postgres app has no home here: no managed Postgres, only per-app `bun:sqlite`. (#11698) |
| 54 | 55 | - A file and upload copy with a manifest compared on both ends. (#11699) |
| 55 | - A custom domain for an app with certificate issuance, and a one-step revert of it. (#11700) | |
| 56 | 56 | - Per-app encrypted backup with a restore proof (`docs/features/backup-truth.md` covers the forge's own, not an app's). (#11701) |
| 57 | 57 | - A rehearsal mode: a preview seeded from a copy of production data, with payment sandbox keys only. (#11702) |
| 58 | 58 | - Inbound payment webhooks that survive a preview's changing address. (#11703) |
new above
offline
Repoint to the new features doc once the section moves; this line is also very long, split it.