Skip to content

feat(deployment): add Coolify Compose templates for Postgres and SQLite - #1

Merged
flyingwebie merged 1 commit into
mainfrom
feat/coolify-compose-stack
Sep 1, 2026
Merged

flyingwebie merged 1 commit into
mainfrom
feat/coolify-compose-stack

Conversation

@flyingwebie

Copy link
Copy Markdown
Owner

Lands the Coolify deployment target on our main so follow-up work can build on it while the upstream review runs.

Same commit submitted upstream as CoreBunch#475.

Adds docker-compose.coolify.yml (bundled Postgres) and docker-compose.coolify.sqlite.yml (single container), docs/deployment/coolify.md, the Coolify rows in docs/deployment/README.md, release-bundle wiring, and a coolify docker config test suite.

Verification: bun run build clean, bun test 6747 pass / 0 fail, bun run lint clean, and docker compose config validates both Coolify files.

🤖 Generated with Claude Code

Coolify could not run any of the existing Compose files. It treats one
file as the single source of truth, so the `-f compose.prod.yml -f
compose.sqlite.yml -f compose.tls.yml` layering has nowhere to go; it
runs its own Traefik, so the Caddy container and its `./Caddyfile` bind
mount are redundant; and `ports:` would publish Postgres on the host,
bypassing the proxy the platform exists to manage. Two standalone files
instead, one per database engine.

PUBLIC_ORIGIN is the reason these files are worth shipping rather than
leaving to each operator. `resolvePublicOrigins` auto-detects Render and
Railway and has no Coolify branch, and `server/auth/security.ts`
deliberately never trusts X-Forwarded-Proto. Coolify's Traefik terminates
TLS and forwards plain HTTP, so an unset PUBLIC_ORIGIN leaves the server
believing it is http:// and four things degrade — only one of them
loudly: the session cookie silently drops its Secure flag, the CSRF check
compares the wrong expected origin, real-time co-editing never connects
because the WebSocket upgrade runs that same origin guard, and MCP
connector URLs resolve local-only. Wiring `SERVICE_URL_INSTATIC_3001`
alongside `PUBLIC_ORIGIN=${SERVICE_URL_INSTATIC}` fixes all four and
follows a custom domain set later in the UI.

INSTATIC_SECRET_KEY is the other one that has to be right the first time.
The image runs NODE_ENV=production, where masterKey.ts throws at boot
unless the value decodes to exactly 32 bytes. Coolify's REALBASE64_32
emits base64 of 32 random bytes; the similarly-named BASE64_32 emits a
bare 32-character string that is not base64 at all and would fail
validation. Its own Plausible template uses REALBASE64_32 for an
identically-shaped key, so this is precedent rather than inference.

The SQLite template mounts /app/uploads and /app/data rather than the
single /app/storage root the Railway and Render templates use. That
layout exists because those platforms allow one disk, and it needs
RAILWAY_RUN_UID=0 to work at all: a volume mounted where the image has
no directory is created root-owned, and the non-root `bun` user cannot
write to it. The Dockerfile already creates and chowns both of these
paths before dropping privileges.

The gate in dockerConfig.test.ts covers the invariants that fail
silently rather than loudly — a stray `networks:` block (Coolify's
documented cause of intermittent HTTPS outages), a `ports:` mapping, a
missing PUBLIC_ORIGIN, the wrong base64 variable, or a drift back to
/app/storage. Comments carry the reasoning so the next reader knows why
the rule exists.

Verified against the published image rather than by inspection: both
stacks reach healthy, the Postgres stack applies 24 migrations into 38
tables, volumes come up bun-owned and writable, /health answers 200, and
no host port is published. Documented that the image is linux/amd64 only,
which is not incidental — pulling it on arm64 fails outright.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@flyingwebie
flyingwebie merged commit 1215c2b into main Sep 1, 2026
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