← Changelog

Reach University Gets a Wiki — and Outline Taught Us Two Traps That Make a Failed Boot Permanent

Reach's homebase went from one card to three. Getting there cost a phantom Docker tag and a Redlock deadlock that turns one bad boot into an infinite one — both now written into the runbook so the next client's Outline is a repeat, not a re-fight.

· Michael Staton · with Claude Code on Claude Opus 5

Why Care?

Reach University had exactly one self-hosted tool: a CRM. Today it has three addresses, all under a name a human can read:

lossless.at/reach-edu/crm      → Twenty
lossless.at/reach-edu/wiki     → Outline   ← new
lossless.at/reach-edu/postiz   → Postiz    ← was only reachable at a didi.sh host

Outline is the one that unlocks agents: unlike Docmost (deleted the same day it was deployed, 2026-07-27), its REST API and API keys are free in self-hosting. That was the whole reason for the switch, and Reach now has it.

What's New?

  • Outline deployed into the reach-edu Railway project — outline (outlinewiki/outline:1.9.2, volume /var/lib/outline/data), outline-postgres (16, volume), outline-redis (7, volumeless). Migrations ran to completion; all four internal services (collaboration, websockets, worker, web) start clean; root URL serves 200.

  • Reach's Postiz now answers under lossless.at. Its *.didi.sh host isn't a leftover to clean up — Postiz derives its auth cookie Domain from FRONTEND_URL, and *.up.railway.app is a Public Suffix apex browsers reject, so it needs a real host to function. The path model fits that exactly: pretty address in front, whatever host the tool demands behind it.

Gotchas — both make a failure permanent, not transient

  1. The Docker tag has no v. GitHub releases Outline as v1.9.2; Docker Hub publishes 1.9.2. Deploying outlinewiki/outline:v1.9.2 fails at image pull with no log line at all — the deployment simply reads FAILED. Read the tag off Docker Hub, never the release page.

  2. A failed first boot deadlocks every boot after it. Before checking migrations, Outline takes a Redlock lock on the redis key migrations (TTL ~180s). A container that dies holding it leaves the lock live, and the next container's retry window is shorter than the TTL — so it logs unable to achieve a quorum during its retry window and quits. Forever. Restarting only feeds the loop. Break it: stop the service, poll redis-cli TTL migrations until -2, then mint exactly one deploy.

Corrections to our own runbook

  • railway redeploy really does re-run the old spec — including its empty variable set. The warning in docs/outline/setup.md step 4 was right and got re-proven the hard way. CONFIG_EPOCH is the only reliable trigger.

  • The redis start command is not load-bearing. The runbook prescribes redis-server --maxmemory-policy noeviction; this deployment set none and redis still reports noeviction (the default when no maxmemory is set), plus protected-mode=no and bind=* -::*. Set it for uniformity across bundles, not for correctness — and don't chase it as a cause.

  • Tooling floor is now Railway CLI ≥5.30. On 4.8.0 the failure above is undiagnosable: railway logs <id> returns "Deployment id does not exist" for every deployment, and there is no way to change a service image or delete a service. 5.30 adds service source connect --image, service status, working logs --lines, and ssh -- <cmd>. Also: the CLI's stored session token is not valid for the public GraphQL API (403) — that needs a dashboard token.

Left open

The operator gate is unchanged and still requires a human: open lossless.at/reach-edu/wiki, complete the first-run Create workspace screen, then wire an auth provider (Google OAuth or SMTP magic links) before the admin session expires. palmer-ai has been stuck at exactly that step since 2026-07-27 — worth not repeating. After login, mint the API key and add pg-dump-outline to Reach's backup crons, which today cover only Twenty.

Files changed

  • docs/outline/setup.md
  • hubs/lossless-at/src/config/clients.ts
  • client-stacks/reach-edu/outline/ (gitignored)