Suite 42 — Dockerize the stack (+ suite 41 refactor, human docs) - #41
Merged
Conversation
Replaces the seven systemd units + hand-edited host nginx with a single
docker-compose service graph. Postgres stays external (Neon); Redis, nginx,
gunicorn, all four Celery roles and Next.js move into containers.
New:
- backendServer/Dockerfile — multi-stage `app` (gunicorn/celery/beat, no
Chromium) and `playwright` (FROM app + bundled Chromium) for the broadcast
and scrape workers.
- Dockerfile.frontend — `commons-runtime` (Next.js standalone) and
`broadcast-build` (SPA, build-only). Separate pnpm workspaces/lockfiles.
- deploy/nginx/{Dockerfile,thecommons.conf} — nginx as sole ingress, baking in
collectstatic output and the broadcast SPA via Buildx named build contexts.
- docker-compose.yml (prod) + docker-compose.override.yml (local dev only).
- .dockerignore, backendServer/.dockerignore.
- docs/adr/0001-containerization.md.
Changed: ci.yml deploy job builds images and runs `up -d` over SSH;
healthcheck.{sh,service,timer} check containers instead of systemd units;
next.config.ts gains `output: 'standalone'`.
Includes an auth.thecommons.town server block that the initial config
omitted — the host nginx served that hostname, and dropping it would have
fallen through to the www->apex redirect, breaking Better Auth sign-in and
the JWKS endpoint every broadcast JWT verification depends on.
DEPLOY.md corrected for two things found while prepping the real VM:
`ubuntu` is uid 1001 (not 1000 — `opc` holds 1000), so the documented
chown locked the deploy user out of its own bind mounts; and the compose
version assertion no longer pins a major.
Co-Authored-By: Claude <noreply@anthropic.com>
UserProfile moved events -> accounts, and the digest fan-out tasks moved events -> newsletter (repointed by newsletter/migrations/0002). Both docs still pointed at the old paths. Co-Authored-By: Claude <noreply@anthropic.com>
None of these were visible locally — all three came out of running the
cutover against the actual Oracle box.
1. Build args silently emptied by shell sourcing. `set -a; . env_file` is
shell sourcing, not a dotenv parser: theCommonsWeb/.env.local had a BARE
DATABASE_URL whose Neon query string contains `&`, so the shell read it as
"assign in a background subshell, then assign channel_binding" and the
variable never reached the deploy script. Compose's ${VAR:-placeholder}
defaults then turn that into a build that SUCCEEDS with placeholder config
baked into the image. The deploy job now fails loudly if any required build
arg is empty, and DEPLOY.md documents the quoting rule plus an audit
snippet that compares each key's literal value against what sourcing
actually yields.
2. Smoke test never probed auth.thecommons.town. Added a direct check of
/api/auth/jwks — the URL backendServer/.env points BETTER_AUTH_JWKS_URL at,
so every broadcast JWT verification depends on it. Previously a broken auth
origin only surfaced indirectly, as a 500 from the garbage-JWT probe.
3. DEPLOY.md ordering hazards, now called out where they bite:
- §6's Redis repoint breaks the still-running systemd stack the moment it's
saved (host has no `redis` hostname) — added the /etc/hosts bridge.
- Checking a branch out on the VM swaps files under a live gunicorn, which
imports lazily; a branch with an app-layout change 500s until restart.
- `ubuntu` is uid 1001 here, not 1000 (documented separately in 15f82ee).
Co-Authored-By: Claude <noreply@anthropic.com>
The lint job was failing on this branch before suite 42 touched it (E501 x2 in devtools tests, I001 import ordering x3), which gated every other CI job as SKIPPED and blocked the merge. All mechanical: `ruff check --fix` plus `ruff format`. No behavior change. ruff check + ruff format --check + mypy all clean locally afterward. Co-Authored-By: Claude <noreply@anthropic.com>
Suite 42's cutover was executed on the real VM, so its ledger row moves from Needs QA to In Prod and records the five VM-only bugs the cutover surfaced. Suite 44 collects what it left behind: the pending kernel reboot, the stray untracked events.0023 migration, and the untested calendar work that rode along on the merge. OUTBOX.md is gitignored; its matching MOVE SUITE / UPDATE / NEW SUITE blocks are appended there for the desktop app to apply. Co-Authored-By: Claude <noreply@anthropic.com>
aryavenkatesan
added a commit
that referenced
this pull request
Aug 2, 2026
Classification + build pass over ~48 candidate Triangle-area sources, with per-source verdicts, the confirmed ICS feed list, and the platform gotchas worth carrying into the next batch. Status section reflects the PR #41 merge and the five sources wired up in prod. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Containerizes the whole stack (suite 42) and merges the in-flight work already on this branch: the suite-41 domain-boundary refactor, human-facing docs, and calendar work.
Seven systemd units + a hand-edited host nginx are replaced by one
docker-compose.yml. Postgres stays external on Neon.Status: already live and verified on the production VM
Rather than merge and hope, the cutover was performed by hand on the Oracle box first, so
mainis only being armed against a VM that is already proven. Currently green there:deploy/healthcheck.shreports all checks passedpg_dumptaken first)pnpm buildverified with the newoutput: 'standalone'Five problems found by running it, not reading it
auth.thecommons.townwas missing from the container nginx config. The host served it; the container did not. Cutting over as-is would have dropped it to thewww→apex redirect — breaking Better Auth sign-in and the JWKS endpoint every broadcast JWT verification fetches. Added the server block plus a direct smoke-test probe.DATABASE_URLintheCommonsWeb/.env.localwas unquoted and contains&.set -a; . fileis shell sourcing, not dotenv parsing, so the assignment ran in a background subshell and never reached the deploy script. Compose's${VAR:-placeholder}default would then have produced a successful build with placeholder config baked in. File fixed; deploy job now fails loudly on any empty build arg.ubuntuis uid 1001 on this image, not 1000 (opcholds 1000). DEPLOY.md'schown 1000:1000locked the deploy user out of its own bind mounts, silently breaking backup pruning. Now split ownership + setgid.redishostname. Documented, with an/etc/hostsbridge for the window before cutover.apt-get updateheld the apt lock and blocked Docker install outright.Note
247cc86 feat: many calendar impls that aren't testedrides along on this branch. It is already running on the VM and the box is healthy, but it is untested by its own commit message and worth a follow-up.🤖 Generated with Claude Code