Skip to content

Suite 42 — Dockerize the stack (+ suite 41 refactor, human docs) - #41

Merged
aryavenkatesan merged 8 commits into
mainfrom
all-things-ingestion
Aug 2, 2026
Merged

Suite 42 — Dockerize the stack (+ suite 41 refactor, human docs)#41
aryavenkatesan merged 8 commits into
mainfrom
all-things-ingestion

Conversation

@aryavenkatesan

Copy link
Copy Markdown
Collaborator

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 main is only being armed against a VM that is already proven. Currently green there:

  • All 8 services running; deploy/healthcheck.sh reports all checks passed
  • 6 pending suite-41 migrations applied to prod (pre-migrate pg_dump taken first)
  • All 5 origins correct through Cloudflare, incl. SPA deep-link fallback and baked static
  • Chromium 148 launches in both Playwright workers; 3 distinct Celery nodes each draining only its own queue
  • Redis DB 0 (broker) + DB 1 (cache) both exercised
  • Backend suite green: 699 tests
  • Host pnpm build verified with the new output: 'standalone'

Five problems found by running it, not reading it

  1. auth.thecommons.town was missing from the container nginx config. The host served it; the container did not. Cutting over as-is would have dropped it to the www→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.
  2. DATABASE_URL in theCommonsWeb/.env.local was unquoted and contains &. set -a; . file is 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.
  3. ubuntu is uid 1001 on this image, not 1000 (opc holds 1000). DEPLOY.md's chown 1000:1000 locked the deploy user out of its own bind mounts, silently breaking backup pruning. Now split ownership + setgid.
  4. DEPLOY.md §6 breaks the live host stack the moment it is saved — the host has no redis hostname. Documented, with an /etc/hosts bridge for the window before cutover.
  5. A 12-day-hung apt-get update held the apt lock and blocked Docker install outright.

Note

247cc86 feat: many calendar impls that aren't tested rides 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

aryavenkatesan and others added 8 commits August 1, 2026 01:34
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
aryavenkatesan merged commit 34f04d2 into main Aug 2, 2026
5 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant