Doc updates

This commit is contained in:
adilallo
2026-05-23 14:55:19 -06:00
parent a14cae744d
commit a3119d3f90
3 changed files with 165 additions and 53 deletions
+21 -14
View File
@@ -668,9 +668,9 @@ All six are titled `[Backend] …`, assigned to Vinod, in the **community-rule**
| # | Linear | Title | Depends on |
| - | ------ | ----- | ---------- |
| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `[Backend] Bridge CLOUDRON_* env vars to canonical names` | none — can ship now |
| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | `[Backend] Container image registry: choose, build, push` | registry decision (handoff §5) |
| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` | CR-96 + CR-97 + Cloudron CLI access + staging DNS |
| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `[Backend] Cloudron-native env vars` | **Done** — merged |
| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | `[Backend] Container image registry: choose, build, push` | **Done** — first image `0.1.0` verified |
| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` | Cloudron CLI token (§2) — **next** |
| 4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | `[Backend] Cloudron production install + apex cutover` | CR-98 green for the agreed overlap window |
| 5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | `[Backend] Steady-state operator runbook` | CR-98 (write what we actually did) |
| 6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | `[Backend] Decommission legacy CommunityRule LAMP app` | CR-99 + sign-off window |
@@ -682,21 +682,28 @@ All six are titled `[Backend] …`, assigned to Vinod, in the **community-rule**
| Order | Linear | Repo PR / branch | Kind | Status | Blocked by |
| ----- | ------ | ---------------- | ---- | ------ | ---------- |
| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `adilallo/Backend/BridgeCloudronEnv`*[Backend] Cloudron-native environment variables* | repo | **Open** | — |
| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | TBD — registry choice + build/push (Dockerfile / CI) | repo | **Next** | CR-96 merged + registry decision ([ops-backend-deploy.md](ops-backend-deploy.md) §6) |
| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `adilallo/Backend/BridgeCloudronEnv`*[Backend] Cloudron-native environment variables* | repo | **Done** | — |
| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container registry packaging + `docker-release.sh` | repo | **Done** | |
| — | [CR-102](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export) | TBD — optional repo PR if export tooling/docs needed | product / repo | **Parallel** | row count from legacy MySQL (preCR-99 backup) |
| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | — (ops checklist; doc tweaks only if smoke finds gaps) | ops | Backlog | CR-96 + CR-97 + Cloudron CLI token + staging DNS |
| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | — (ops checklist; [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98)) | ops | **Next** | Cloudron CLI token only |
| 4 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | TBD — `docs/guides/ops-runbook.md` | docs | Backlog | CR-98 (write what we actually did) |
| 5 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | — (ops; maintenance window) | ops | Backlog | CR-98 green + CR-102 resolved |
| 6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | — (ops; uninstall LAMP slot) | ops | Backlog | CR-99 + sign-off window |
**What's next:** merge **CR-96** PR, then open **CR-97** on its own branch. Start **CR-102** product decision in parallel so it is resolved before the CR-99 cutover window.
**What's next:** **CR-98** — staging install + smoke at `staging.communityrule.info`
([ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98)).
Start **CR-102** product decision in parallel so it is resolved before the CR-99
cutover window.
**Per-ticket detail:**
1. **Cloudron-native env vars (CR-96).** **Shipped in repo** on branch `adilallo/Backend/BridgeCloudronEnv` (PR open). App reads `CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only (no `DATABASE_URL` / `SMTP_URL` shim). Local dev uses the same names in `.env`. SMTP URL assembled in [`lib/server/env.ts`](../../lib/server/env.ts); mail senders use `getSmtpUrl()`. Acceptance: with only `CLOUDRON_*` set, app connects to DB and sends mail; unit tests in `tests/unit/env.test.ts`.
2. **Container image registry: choose, build, push.** Acceptance: `docker pull <registry>/communityrule:<tag>` works from a Cloudron-reachable network. CI builds and pushes on merge to `main` (stretch).
3. **Cloudron staging install + smoke.** Acceptance: `curl https://<staging>/api/health` returns `{"ok":true,"database":"connected"}`; magic-link request → click link → `GET /api/auth/session` returns a user; publishing a rule succeeds.
1. **Cloudron-native env vars (CR-96).** **Done.** App reads `CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only (no `DATABASE_URL` / `SMTP_URL` shim). Prisma datasource uses `CLOUDRON_POSTGRESQL_URL`. Local dev uses the same names in `.env`. SMTP URL assembled in [`lib/server/env.ts`](../../lib/server/env.ts); mail senders use `getSmtpUrl()`. `scripts/start.sh` does not bridge env names.
2. **Container image registry (CR-97).** **Done.** Gitea registry on `git.medlab.host`; repo `CommunityRule/community-rule` is **public** (package visibility inherits from repo). Image `git.medlab.host/communityrule/community-rule:0.1.0`, built `linux/amd64` via `./scripts/docker-release.sh`. Anonymous pull verified. CI on merge to `main` deferred (no hosted runners).
3. **Cloudron staging install + smoke (CR-98).** **Next.** Full checklist in [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98). Summary:
- **Prereqs:** Cloudron CLI token (only outstanding item); CR-96 + CR-97 done.
- **Install:** `cloudron install --location staging.communityrule.info` from repo root (manifest supplies `dockerimage`; migrations run in `start.sh`).
- **Configure:** `SESSION_SECRET`, `SMTP_FROM`, `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true`, `UPLOAD_ROOT=/app/data/uploads`.
- **Acceptance:** `GET /api/health``{"ok":true,"database":"connected"}`; magic-link sign-in end-to-end; publish a rule succeeds.
4. **Cloudron production install + DNS cutover.** Acceptance: production subdomain resolves to the new app; old subdomain still works during overlap; sign-in + publish succeed against production; backups confirmed.
5. **Steady-state operator runbook.** Lives at `docs/guides/ops-runbook.md` (sibling to the handoff). Covers deploy a new version, rollback, restore drill cadence, multi-instance limitations from [`backend-roadmap.md`](backend-roadmap.md) §5/§7. Acceptance: a fresh reader can deploy + roll back using only this doc.
6. **Decommission legacy Express/MySQL backend.** Acceptance: old Cloudron app stopped + uninstalled; old MySQL addon backed up once and removed; legacy Gitea repo README updated to point at this app. Priority: Low.
@@ -822,7 +829,7 @@ Tickets **1011** can be deferred without blocking the core “auth + drafts +
## Linear (Community-rule team)
**Main chain (historical):** **CR-72 → CR-83** was the original **strict sequence**; **repo + Linear status today:** **CR-72CR-79**, **CR-83**, **CR-84**, **CR-85**, **CR-88**, **CR-89** are **Done**; **CR-77** (publish) **Done**; **CR-80CR-81** remain **Backlog** (web vitals, public rule detail). **CR-82** covered by local `migrate:smoke` (see Ticket 11). **CR-83** (admin handoff) shipped as a narrow handoff sheet; the Cloudron deployment pipeline is split into **`[Backend]` follow-ups CR-96CR-102** — see [PR plan (CR-96 CR-102)](#pr-plan-cr-96--cr-102) under Ticket 12 (**CR-96** repo PR open; **CR-97** next). **Parallel (still open):** **CR-86** / Ticket 15 (**Backlog** — publish **not** a blocker); **CR-103** / Ticket 20 (change account email); **CR-93** (**Backlog**); **CR-90** / Ticket 18 (stakeholder invites); **CR-91** / Ticket 19 (`Add` button behavior); **Ticket 21 / [CR-113](https://linear.app/community-rule/issue/CR-113/backend-create-flow-file-uploads-community-photo-custom-method)** (create-flow file uploads).
**Main chain (historical):** **CR-72 → CR-83** was the original **strict sequence**; **repo + Linear status today:** **CR-72CR-79**, **CR-83**, **CR-84**, **CR-85**, **CR-88**, **CR-89** are **Done**; **CR-77** (publish) **Done**; **CR-80CR-81** remain **Backlog** (web vitals, public rule detail). **CR-82** covered by local `migrate:smoke` (see Ticket 11). **CR-83** (admin handoff) shipped as a narrow handoff sheet; the Cloudron deployment pipeline is split into **`[Backend]` follow-ups CR-96CR-102** — see [PR plan (CR-96 CR-102)](#pr-plan-cr-96--cr-102) under Ticket 12 (**CR-96** + **CR-97** **Done**; **CR-98** **next**). **Parallel (still open):** **CR-86** / Ticket 15 (**Backlog** — publish **not** a blocker); **CR-103** / Ticket 20 (change account email); **CR-93** (**Backlog**); **CR-90** / Ticket 18 (stakeholder invites); **CR-91** / Ticket 19 (`Add` button behavior); **Ticket 21 / [CR-113](https://linear.app/community-rule/issue/CR-113/backend-create-flow-file-uploads-community-photo-custom-method)** (create-flow file uploads).
| Doc ticket | Linear | Title (short) | Deploy PR / tracking |
| ---------: | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------- |
@@ -838,9 +845,9 @@ Tickets **1011** can be deferred without blocking the core “auth + drafts +
| 10 | [CR-81](https://linear.app/community-rule/issue/CR-81/backend-public-rule-detail-page-get-apirulesid-optional) | Public rule detail (optional) | — |
| 11 | [CR-82](https://linear.app/community-rule/issue/CR-82/backend-ci-postgres-migration-smoke-optional) | Local migrate smoke (**Done in repo**; optional remote CI) | — |
| 12 | [CR-83](https://linear.app/community-rule/issue/CR-83/backend-stagingproduction-runbook-admin-handoff-docsops-backend) | Ops admin handoff (Cloudron) **Done** | — |
| 12.1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | Cloudron-native env vars | **Open**`adilallo/Backend/BridgeCloudronEnv` |
| 12.2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container image registry + CI | **Next**own branch after CR-96 |
| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | Cloudron staging install + smoke | Ops — after CR-96 + CR-97 |
| 12.1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | Cloudron-native env vars | **Done** |
| 12.2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container image registry + CI | **Done**image `0.1.0` |
| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | Cloudron staging install + smoke | **Next** — [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98) |
| 12.4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | Production install + apex cutover | Ops — after CR-98 + CR-102 |
| 12.5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | Steady-state operator runbook | Docs PR — after CR-98 |
| 12.6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | Decommission legacy LAMP app | Ops — after CR-99 + sign-off |
+140 -36
View File
@@ -3,8 +3,10 @@
This doc captures everything needed to deploy the new CommunityRule
(Next.js + Postgres) onto MEDLab's Cloudron and replace the legacy
LAMP-packaged service at `communityrule.info`. Cloudron admin access
has been granted and the container registry is wired up (§6, §9); the
remaining gates are CR-96 (env bridging) and CR-98 (staging install).
has been granted, CR-96 (Cloudron-native env vars) and CR-97 (container
registry + first image push) are done; the remaining gate is
[CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke)
(staging install + smoke — §10).
> **For a plain-language summary to hand to MEDLab's Cloudron admin,
> see [`../relaunch-brief.md`](../relaunch-brief.md).** This doc is the
@@ -76,10 +78,12 @@ per-app in the manifest and provisioned at install time.
with the legacy service; SES relay accepts it).
- `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` — turns on Postgres draft
persistence for signed-in users. Required in production.
- `UPLOAD_ROOT` — absolute path to a writable directory (typically on the
Cloudron **localstorage** mount) for `POST /api/uploads` (community photo +
custom-method attachments). When unset, upload routes return
`server_misconfigured`. See [CONTRIBUTING.md](../../CONTRIBUTING.md) API table.
- `UPLOAD_ROOT` — absolute path to a writable directory on the Cloudron
**localstorage** mount for `POST /api/uploads` (community photo +
custom-method attachments). Use **`/app/data/uploads`** on Cloudron
(`start.sh` chowns `/app/data` for the `node` user). When unset, upload
routes return `server_misconfigured`. See [CONTRIBUTING.md](../../CONTRIBUTING.md)
API table.
## 4. Platform settings
@@ -89,9 +93,10 @@ per-app in the manifest and provisioned at install time.
([`app/api/health/route.ts`](../../app/api/health/route.ts) returns
`200 {"ok":true,"database":"connected"}` when healthy, `503`
otherwise).
- Memory limit: start at **512 MiB** (matches what the legacy LAMP
app has been running fine on for two years); raise if Next.js
standalone OOMs under load.
- Memory limit: **768 MiB** in
[`CloudronManifest.json`](../../CloudronManifest.json) (`memoryLimit:
805306368`). The legacy LAMP app ran at 512 MiB; raise further only if
Next.js standalone OOMs under load.
- Backups: Cloudron's automatic backups are already on for the host
(legacy app shows weekly snapshots ~451 MB each). Same default
applies to new apps.
@@ -107,10 +112,14 @@ apex.
### Phases
1. **Staging install**`cloudron install --image <our-image>
--location staging.communityrule.info`. Set env vars from §3. Run
`prisma migrate deploy`. Smoke per
[CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke).
1. **Staging install**from a checkout whose
[`CloudronManifest.json`](../../CloudronManifest.json) matches the pushed
image tag, run `cloudron install --location staging.communityrule.info`.
Cloudron reads `dockerimage` from the manifest (no `--image` flag). Set
manual env vars from §3. `prisma migrate deploy` runs automatically in
[`scripts/start.sh`](../../scripts/start.sh) on container start. Smoke per
[CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke)
(§12).
2. **Soft launch / acceptance** — share the staging URL with a small
group, exercise sign-in + publish + draft sync end-to-end. Hold
here until confident.
@@ -152,18 +161,24 @@ Product decisions (closed):
Infra decision closed:
3. **Container registry — Gitea Container Registry on `git.medlab.host`.**
Same host as Cloudron (`193.46.198.90`); container package is set
**public** to sidestep the [same-host docker-login "socket hangup"
Same host as Cloudron (`193.46.198.90`). The
[`CommunityRule/community-rule`](https://git.medlab.host/CommunityRule/community-rule)
repo must be **public** so the container package inherits public visibility
(Gitea does not expose per-package visibility toggles — visibility follows
the owning repo). Public pull sidesteps the [same-host docker-login
"socket hangup"
bug](https://forum.cloudron.io/topic/14572/private-docker-registry-in-cloudron),
so Cloudron pulls without credentials. Push auth from operator
laptops uses a Gitea personal access token (`read:package` +
`write:package`). Canonical image ref:
`git.medlab.host/communityrule/community-rule:<tag>`. Operator
build/push workflow lives in [§9](#9-build-and-push-image-workflow).
Tracked in [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push).
Fallback if same-host pull ever breaks: install the **Cloudron
Container Registry** app and re-tag against its hostname; no other
changes required.
so Cloudron pulls without credentials. Push auth from operator laptops uses
a Gitea personal access token (`read:package` + `write:package`). Canonical
image ref: `git.medlab.host/communityrule/community-rule:<tag>`. Images are
built **`linux/amd64` only** (Cloudron host is x86_64). Operator build/push
workflow lives in [§9](#9-build-and-push-image-workflow). First verified
image: `…:0.1.0` (digest
`sha256:e652f9f4bfa4154412cc9d8b63d55c94a128e8935579d101b5ab8977e2080e52`).
Tracked in [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push)
(**Done**). Fallback if same-host pull ever breaks: install the **Cloudron
Container Registry** app and re-tag against its hostname; no other changes
required.
## 7. Old vs new deltas
@@ -199,17 +214,16 @@ All filed in Linear, titled `[Backend] …`, assigned to me, in the
**Community-rule** team, **Backlog** state.
1. [**CR-96**](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names)
— `[Backend] Cloudron-native env vars` (shipped: app reads
`[Backend] Cloudron-native env vars` (**Done** — app reads
`CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only).
2. [**CR-97**](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push)
— `[Backend] Container image registry: choose, build, push`.
Registry decided (§6.3); packaging + build/push workflow shipped
(§9). Closes after the first verified `docker pull` of the pushed
image (no Cloudron-side install required to close this ticket;
that's CR-98).
`[Backend] Container image registry: choose, build, push` (**Done**).
Registry decided (§6.3); packaging + build/push workflow shipped (§9).
First image pushed and verified via anonymous `docker pull` (§9).
3. [**CR-98**](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke)
`[Backend] Cloudron staging install + smoke` at
`staging.communityrule.info`. Blocked by CR-96 + CR-97.
`staging.communityrule.info`. **Next** — checklist in §10. Requires
Cloudron CLI token (§2) only; CR-96 and CR-97 are done.
4. [**CR-99**](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)
`[Backend] Cloudron production install + apex cutover`.
Side-by-side cutover at scheduled low-traffic window per §5.
@@ -273,16 +287,24 @@ standalone server.
Override the tag with `TAG=v0.1.1 ./scripts/docker-release.sh` for
semver releases. The script prints the exact `dockerimage` line to
paste back into the manifest.
3. **First push only:** in Gitea, navigate to the `CommunityRule` org
→ Packages → `community-rule` → Settings → set **Visibility: Public**.
3. **First push only:** confirm the
[`CommunityRule/community-rule`](https://git.medlab.host/CommunityRule/community-rule)
repo is **Public** (Settings → General). Gitea inherits container-package
visibility from the repo — there is no per-package visibility toggle. Org
owners are not required if you have repo-admin rights on this repo.
4. **Verify the pull works without credentials** (simulates Cloudron's
anonymous pull):
```bash
docker logout git.medlab.host
docker pull git.medlab.host/communityrule/community-rule:<tag>
# Image is linux/amd64 only. On Apple Silicon, add --platform:
docker pull --platform linux/amd64 git.medlab.host/communityrule/community-rule:<tag>
```
A bare `docker pull` on arm64 Macs fails with "no matching manifest for
linux/arm64" — that is expected and does **not** indicate an auth problem.
Cloudron (x86_64) pulls the amd64 manifest without `--platform`.
5. **Commit the manifest change** alongside any code changes that
shipped in this build, so the manifest and image stay in lockstep.
@@ -311,13 +333,95 @@ today, and the manual workflow above is acceptable for v1 staging and
production. Revisit when runners return or when release cadence
justifies the runner cost.
## 10. Rate limiting (single-instance deploys)
## 10. Staging install + smoke (CR-98)
**Goal:** Install the pushed image at `staging.communityrule.info`, configure
production env vars, and verify the vertical slice before apex cutover
([CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)).
**Prerequisites (all satisfied unless noted):**
- [x] **CR-96** — app reads `CLOUDRON_POSTGRESQL_URL` and
`CLOUDRON_MAIL_SMTP_*` only ([`lib/server/env.ts`](../../lib/server/env.ts),
[`prisma/schema.prisma`](../../prisma/schema.prisma)). No `DATABASE_URL` /
`SMTP_URL` shim.
- [x] **CR-97** — image pushed to
`git.medlab.host/communityrule/community-rule:0.1.0` (or current tag in
manifest); repo is **public**; anonymous amd64 pull verified (§9).
- [ ] **Cloudron CLI token** — generate at *Profile → API Tokens* on
`cloud.medlab.host`; save in 1Password (§2).
- [x] **Cloudron admin login** on `cloud.medlab.host` (§2).
- [x] **DNS** — `communityrule.info` managed in Cloudron; staging subdomain
will be provisioned at install time.
**Install steps:**
1. **Checkout** a commit whose [`CloudronManifest.json`](../../CloudronManifest.json)
`version`, `dockerimage`, and `memoryLimit` match the image you intend to
run (currently `0.1.0` →
`git.medlab.host/communityrule/community-rule:0.1.0`).
2. **Log in to Cloudron CLI:**
```bash
cloudron login cloud.medlab.host
```
3. **Install** from the repo root (manifest is read automatically):
```bash
cloudron install --location staging.communityrule.info
```
Cloudron provisions **postgresql**, **sendmail**, and **localstorage**
addons from the manifest, pulls the image (no registry credentials needed),
and starts the container. `scripts/start.sh` chowns `/app/data`, runs
`prisma migrate deploy`, then execs the Next.js server.
4. **Set manual env vars** (Cloudron does not inject these):
```bash
cloudron configure --app <app-id> --set-env \
SESSION_SECRET="$(openssl rand -hex 32)" \
SMTP_FROM="Community Rule <hello@communityrule.info>" \
NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true \
UPLOAD_ROOT=/app/data/uploads
```
Rotating `SESSION_SECRET` logs everyone out. `SMTP_FROM` must be an address
SES accepts on `communityrule.info` (platform mail addon is SES-relayed with
custom-from allowed — §3).
5. **Confirm the app is running** in the Cloudron dashboard (Logs tab). Look
for a clean `prisma migrate deploy` and Next.js listening on port 3000.
**Smoke checklist (acceptance):**
- [ ] **Health:** `curl -sS https://staging.communityrule.info/api/health`
returns `200` with `{"ok":true,"database":"connected"}`.
- [ ] **Magic link:** request sign-in from the UI → email arrives at a real
inbox → click link → land signed in →
`GET /api/auth/session` returns a user. Confirm the link host matches
`staging.communityrule.info` (reverse proxy / `Host` alignment).
- [ ] **Publish:** complete create flow → publish a rule → public rule detail
loads.
- [ ] **Draft sync (optional):** signed-in Save & Exit persists to Postgres;
resume works after re-login.
- [ ] **Upload (optional):** with `UPLOAD_ROOT` set, attach a community photo
in create flow and confirm it renders after publish.
**If something fails:**
| Symptom | Likely cause | Check |
| ------- | ------------ | ----- |
| Image pull error on install | Repo still private, or wrong tag in manifest | §6.3; `docker pull --platform linux/amd64 …` from laptop |
| Health `503` / `database: disconnected` | Postgres addon not provisioned or URL missing | Cloudron app → Environment; expect `CLOUDRON_POSTGRESQL_URL` |
| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars |
| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | Set to `/app/data/uploads` (§3) |
| Container crash on start | Migration failure | App logs around `prisma migrate deploy` |
**Done when:** all smoke checklist items pass. Then proceed to soft-launch
(§5 phase 2) and, when ready, [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)
apex cutover.
## 11. Rate limiting (single-instance deploys)
The app uses an **in-memory** rate limiter in [`lib/server/rateLimit.ts`](../../lib/server/rateLimit.ts) (magic-link requests, organizer inquiry, etc.). This is sufficient for the current **single Cloudron container** per environment.
**Before horizontal scale-out** (multiple app instances behind a load balancer), replace or back the limiter with a shared store (e.g. Redis) so per-IP / per-user windows apply across instances. Until then, document expected limits in the steady-state runbook ([CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook)).
## 11. Related docs
## 12. Related docs
- [`docs/guides/backend-roadmap.md`](backend-roadmap.md) §11
(environments) and §8 (Prisma migrations policy).
+4 -3
View File
@@ -47,9 +47,10 @@ Rollback plan during the window: restore the legacy backup to a scratch Cloudron
Roughly this order:
1. **Code prep**small local change so the app reads Cloudron's injected `CLOUDRON_*` env vars natively. No infra impact.
2. **Build and push the app image** to a container registry.
3. **Install at staging** subdomain, smoke test, soft launch.
1. ~~**Code prep** — Cloudron-native env vars (CR-96).~~ **Done.**
2. ~~**Build and push the app image** to Gitea registry (CR-97).~~ **Done**
(`git.medlab.host/communityrule/community-rule:0.1.0`).
3. **Install at staging** subdomain, smoke test, soft launch (CR-98).
4. **Apex cutover window** — the brief downtime above.
5. **Uninstall legacy**, archive legacy repos.
6. **Write the steady-state runbook** based on what actually worked.