Public: anyone can read this; the crew can change it.
Platform app move gap: Postgres apps have no home on the forge #11709
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/11698-platform-app-move-gap-postgres-apps-have → main 1 commit fork
3 files
changed +31
−2
docs/APPS.md +27
| @@ -1884,6 +1884,33 @@ by the hour. | ||
| 1884 | 1884 | |
| 1885 | 1885 | ## What is deliberately not here |
| 1886 | 1886 | |
| 1887 | **A managed Postgres per app.** Decided (#11698): the forge does not run | |
| 1888 | one. An app's state is `bun:sqlite` under `FORGE_APP_DATA` because that is | |
| 1889 | what makes production data, preview data, backup and restore one directory | |
| 1890 | each, with no second process to run, secure, upgrade or restore. A managed | |
| 1891 | Postgres would add a server, credentials per app, a network path out of the | |
| 1892 | cell and a backup of its own, for apps that are one process on one box. The | |
| 1893 | way for a Postgres app is a documented port, in this order: | |
| 1894 | ||
| 1895 | 1. **Port to `bun:sqlite`** (the default). Keep the schema's tables and | |
| 1896 | constraints, replace Postgres-only types (`jsonb` becomes `TEXT` with | |
| 1897 | `json_*`, `timestamptz` an ISO-8601 or epoch `INTEGER`, `serial` an | |
| 1898 | `INTEGER PRIMARY KEY`, arrays a child table), move queries behind one | |
| 1899 | module, and prove it with characterization tests run on both sides before | |
| 1900 | the switch. Load the dump through a script that reads the `pg_dump --data-only | |
| 1901 | --inserts` output and compares per-table counts and checksums. | |
| 1902 | 2. **Keep an external Postgres** only when the port is not worth it | |
| 1903 | (extensions such as PostGIS, data too large for one file). Declare its | |
| 1904 | host in `forge.json` `egress`; the connection string is an app secret set | |
| 1905 | by name in the app's settings, never in the repository. A preview then | |
| 1906 | shares or lacks that database: point previews at a scratch database, never | |
| 1907 | at production. The forge does not back it up; its owner does. | |
| 1908 | ||
| 1909 | Writes are single-process and WAL mode carries the release overlap, so an app | |
| 1910 | that needs many writers across machines is the case for option 2, not a | |
| 1911 | reason to build a database service into the forge. If many apps hit option 2, | |
| 1912 | reopen this with the count. | |
| 1913 | ||
| 1887 | 1914 | Containers. A cell (rule 9) is namespaces and a watch, not an image: an |
| 1888 | 1915 | app runs the machine's own `bun` and tools, read-only, and its limits are |
| 1889 | 1916 | enforced by stopping it, not by throttling it — a container cannot create |
docs/EXTRACT.md +2 −1
| @@ -105,7 +105,8 @@ https://forge.example/trevato/forge.git main && systemctl restart forge` | ||
| 105 | 105 | forge. |
| 106 | 106 | - **Postgres and object storage per app.** Forge apps keep SQLite under |
| 107 | 107 | `FORGE_APP_DATA`; an app that needs a bucket needs the forge to hand it |
| 108 | one, which it does not yet. | |
| 108 | one, which it does not yet. Postgres is decided against: see | |
| 109 | `APPS.md`, "What is deliberately not here". | |
| 109 | 110 | - **Dashboards.** No Prometheus, no Grafana. The forge's own pages — |
| 110 | 111 | Crew, the audit, the ledger — are the observability it has; a |
| 111 | 112 | `/metrics` for the forge itself is a small addition if wanted. |
docs/features/platform-app-move.md +2 −1
| @@ -23,6 +23,7 @@ Write the answers in the migration issue; names of secrets, never values. | ||
| 23 | 23 | `forge.json` says, and declare `schedules` for its jobs. |
| 24 | 24 | The forge's store is `bun:sqlite`; a Postgres app is ported (the crew's work) |
| 25 | 25 | or keeps an external Postgres reached by an egress entry. The pull says which. |
| 26 | The decision and both paths are in docs/APPS.md, "What is deliberately not here". | |
| 26 | 27 | |
| 27 | 28 | ## 3. Copy the data (person runs, crew writes the script) |
| 28 | 29 | Dump with `pg_dump`, load, then compare per-table row counts and a per-table |
| @@ -50,7 +51,7 @@ counts. The old copy stays until that has passed. | ||
| 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 | - A Postgres app has no home here: no managed Postgres, only per-app `bun:sqlite`. (#11698) | |
| 54 | - ~~A Postgres app has no home here.~~ Decided, not built: no managed Postgres; port to `bun:sqlite` or keep an external one by `egress` (docs/APPS.md). (#11698) | |
| 54 | 55 | - A file and upload copy with a manifest compared on both ends. (#11699) |
| 55 | 56 | - A custom domain for an app with certificate issuance, and a one-step revert of it. (#11700) |
| 56 | 57 | - Per-app encrypted backup with a restore proof (`docs/features/backup-truth.md` covers the forge's own, not an app's). (#11701) |
new above
offline