Postiz Lands on reach-edu — Five Services, Three Gotchas, One Social Command Center
The stack's Hootsuite-killer is live at postiz.reach-edu.didi.sh: Postiz v2.22.1 deployed to the reach-edu Railway project with a full Temporal orchestration layer, after fighting through a search-attribute quota, a soft-deleted volume, Railway's injected PORT, and a session cookie that browsers refused to store.
Why Care?
Postiz has sat on the README's replacement roster (Hootsuite $1.2k–3k/yr,
Buffer $2.4k/yr, Sprout $199+/seat/mo → $0) since the beginning, with the
upstream repo pinned under core/postiz-app. Today it went from roster row
to running software: reach-edu now has a self-hosted social command center
at https://postiz-production-d863.up.railway.app, verified by browser
drive down to the register screen and a live backend API.
What's New?
Five-service bundle in the reach-edu Railway project:
postiz(pinnedghcr.io/gitroomhq/postiz-app:v2.22.1, volume at/uploads),postiz-postgres(17-alpine),postiz-redis(volumeless, Twenty pattern),temporal(temporalio/auto-setup:1.28.1), andtemporal-postgres(16). Postiz v2 schedules posts as Temporal workflows, so the orchestrator is load-bearing, not optional — but we run it with Postgres visibility (ENABLE_ES=false), dropping upstream's Elasticsearch, temporal-ui, and admin-tools services entirely.docs/postiz/setup.md— the per-client runbook, born with a four-entry gotcha log from this first execution:SKIP_ADD_CUSTOM_SEARCH_ATTRIBUTES=trueis mandatory — auto-setup's demo attributes eat 2 of SQL visibility's 3 Text slots, and Postiz's backend crashloops registering its ownorganizationId/postId.Railway volume deletion is a 2-day soft-delete with no public recover mutation and staged-change limbo on detach; recreating the whole postgres service is faster than fighting it.
Railway injects
PORT=8080; Postiz's backend honors it while the container's internal nginx expects 3000 → every API call 502s untilPORT=3000is pinned.${{service.RAILWAY_PRIVATE_DOMAIN}}references resolve to EMPTY (silently) after the referenced service is deleted.Auth is impossible on
*.up.railway.app— Postiz scopes its session cookie to the registrable domain ofFRONTEND_URL, which computes toDomain=.railway.app, a Public Suffix List apex every browser rejects. Registration returns 200, the account exists, and the user stays logged out forever with zero backend errors. The custom domain (postiz.reach-edu.didi.sh, cut over same-day via the custom-domain-cutover playbook) is therefore load-bearing, not cosmetic; the cookie now lands asDomain=.didi.shand sticks. Verified end-to-end with a curl login on the new domain.
Scope Note
Postiz publishes; it does not follow. The companion ambition — following every person and org with profile info in the SurrealDB canonical layer — is a per-platform job (feasible via AT Protocol/ActivityPub for Bluesky/Mastodon, not offered at accessible API tiers by LinkedIn/X) and is explicitly out of this deploy's scope, documented in the runbook.
Next
Operator gate: sign in at postiz.reach-edu.didi.sh/auth/login (the
account created during the cookie saga is intact), then flip
DISABLE_REGISTRATION=true. Per-platform OAuth credentials get collected
as channels are connected. Hub card follows the usual path; the domain is
already done.
Files changed
docs/postiz/setup.mdclient-stacks/reach-edu/stack.md (gitignored)client-stacks/reach-edu/postiz/.env (gitignored)