Skip to content

Add Docker image and self-hosting compose stack for the Node target - #121

Closed
onel wants to merge 3 commits into
devarshishimpi:devfrom
onel:feat/docker-self-hosting-stack
Closed

onel wants to merge 3 commits into
devarshishimpi:devfrom
onel:feat/docker-self-hosting-stack

Conversation

@onel

@onel onel commented Sep 22, 2026

Copy link
Copy Markdown

Closes #108.

Adds a Dockerfile for @codraoss/node-server and a compose stack that brings up Postgres, Redis, and the app.

cp .env.docker.example .env
docker compose up -d

What's here

  • apps/node/Dockerfile — multi-stage. Builds the dashboard and the server bundle, then ships a runner with only the bundle, dist/client, and the node-server workspace's production deps. 191MB image, 27MB of node_modules.
  • docker-compose.yml — postgres, redis, a one-shot migrate, and codra-app.
  • .env.docker.example — every variable the container needs to boot.
  • apps/node/README.md — setup and configuration.
  • .dockerignore, plus a .gitignore exception so .env.docker.example is tracked.

Notes on the implementation

Dashboard build. The image runs npx vite build rather than the root build script, which chains cf-typegen and would pull wrangler into the image.

Migrations. The migration runner is a plain script and isn't part of the tsup bundle, so the runner stage carries packages/db/scripts and packages/db/migrations, and a one-shot migrate service applies them. The app gates on service_completed_successfully, so a first boot lands on a ready schema. Re-running is a no-op.

Readiness. Postgres and Redis are gated on healthchecks rather than bare depends_on, which only waits for container start.

DATABASE_URL is derived from POSTGRES_USER/PASSWORD/DB. Hardcoding it meant changing POSTGRES_PASSWORD alone left migrate and the app on the old credentials, and the app then never started.

Deviations from the issue

  • node:22-alpine and postgres:16-alpine instead of 20 and 15, to match CI.
  • Postgres and Redis are published on 127.0.0.1 rather than all interfaces, so deploying this to a VPS doesn't put the database on the internet. They ship with development credentials; the README says to change POSTGRES_PASSWORD and to drop the ports: entries if host access isn't needed.

Happy to change either.

Also included

9ce0fdc fixes a pre-existing bug in apps/node/src/index.ts: the '/*.svg' and '/*.ico' static routes never matched, since Hono has no suffix matching, so the favicon and the severity icons 404'd. Separate commit, easy to drop if you'd rather it went on its own.

Not in scope

START_WORKER / START_API belong to #107, and the full self-hosting guide to #109. Queued reviews still aren't processed — the Node review runtime is #106/#107 — so jobs land in Redis and wait. The README says so.

Verification

Local, on a clean docker compose up -d --build with volumes removed:

  • migrate applied all 4 migrations and exited 0; app came up healthy
  • /healthz, /, /assets/*, /favicon.svg, /favicon.ico, /icons/* → 200
  • unsigned POST /webhook → 400; unauthenticated GET /api/jobs → 401
  • docker compose down && up → migrations idempotent, app healthy
  • same run with POSTGRES_PASSWORD changed, to check the derived URL
  • npm run lint, npm run typecheck, and the node-server typecheck all pass

Hono has no suffix matching, so the '/*.svg' and '/*.ico' static routes never
matched and every root-level asset vite copies out of public/ returned 404.
Mount them by path instead, and lift the duplicated client root into a const.
Closes devarshishimpi#108.

apps/node/Dockerfile builds the dashboard and the server bundle in a
multi-stage build and ships a runner carrying only the bundle, dist/client,
and the node-server workspace's production dependencies (191MB image, 27MB
of node_modules). It calls 'npx vite build' rather than the root build
script, which chains cf-typegen and would drag wrangler into the image.

docker-compose.yml brings up Postgres, Redis, and the app. Migrations are
not part of the server bundle, so a one-shot migrate service runs them from
the same image and the app gates on it completing; Postgres and Redis are
gated on healthchecks rather than bare depends_on, and both are published
on 127.0.0.1 so a VPS deploy does not expose them.

.env.docker.example documents every variable the container needs to boot.
- Mark the runner's bundle as ESM. tsup emits ESM as index.js, which ran only
  because Node's module-syntax auto-detection covers it on current 20.19+ and
  22.7+ images; a pinned older digest would fail to start.
- Derive DATABASE_URL from POSTGRES_USER/PASSWORD/DB instead of hardcoding the
  credentials. Changing POSTGRES_PASSWORD alone previously left migrate and the
  app authenticating with the old password, and the app never started because
  it gates on migrate completing.
- Stop documenting Redis as session storage: sessions use InMemorySessionStore,
  so a restart signs dashboard users out. Recorded as a known limitation.
- Drop the telemetry block from the env example. Telemetry lives in
  apps/worker and is not in this image, so the opt-out variable did nothing.
- Note that the bundled credentials are development defaults and that the
  published ports can be removed.

@codra-app-personal codra-app-personal Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codra Review

✅ Nothing to flag. Reviewed 7 files (364 changed lines) and found no issues worth raising.

Note

4 files could not be reviewed, so this pass is incomplete.

Reviewed commit: d706f79142

ℹ️ About Codra in GitHub

Your team has set up Codra to review pull requests in this repo. Reviews are triggered when you:

  • Open a pull request for review
  • Mark a draft as ready

Every review posts a summary here. A clean pass also gets a 👍 on the pull request itself.

@devarshishimpi

Copy link
Copy Markdown
Owner

Closing this as well, as part of #123

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants