Skip to content

Phase 3: replace docker image with notes-app + file-api bundle - #8

Merged
kelsi-bizer merged 1 commit into
masterfrom
claude/phase-3-docker-bundle
May 10, 2026
Merged

Phase 3: replace docker image with notes-app + file-api bundle#8
kelsi-bizer merged 1 commit into
masterfrom
claude/phase-3-docker-bundle

Conversation

@kelsi-bizer

Copy link
Copy Markdown
Owner

Summary

Phase 3: replace the Docker image with the post-Logseq stack. The image now ships the notes-app SPA served by nginx, with the file-api sidecar persisting markdown to /brain. After this PR merges, Build-Docker will publish a working ghcr.io/kelsi-bizer/bizeros-knowledge:latest that does what BizerOS Knowledge is supposed to do.

What changed

Dockerfile — full replacement, three stages

  1. notes-app-builder (node:22-alpine + pnpm) — installs packages/notes-app deps from the lockfile, runs vite build, emits dist/.
  2. file-api-builder — installs packages/file-api prod deps only, copies src/.
  3. runtimenode:22-alpine + nginx + tini. SPA bundle lands at /usr/share/nginx/html; file-api at /opt/file-api; nginx config + entrypoint copied in.

HEALTHCHECK hits /api/health every 30s. VOLUME ["/brain"] and ENV BRAIN_DIR=/brain make the persistence point obvious.

docker/nginx.conf

  • /api/ proxied to 127.0.0.1:3000 (the file-api). Configured for SSE: proxy_http_version 1.1, proxy_buffering off, proxy_read_timeout 24h, empty Connection header — /api/watch streams stay alive.
  • SPA fallback: try_files $uri $uri/ /index.html so deep links and hash routes resolve.
  • client_max_body_size 25M matches the file-api's default MAX_BODY_BYTES.

docker/entrypoint.sh

  • mkdir -p $BRAIN_DIR (default /brain) so the file-api watcher and tree endpoint don't ENOENT on first run.
  • Starts node /opt/file-api/src/server.js in the background; traps TERM/INT and forwards them.
  • Polls /api/health for up to 5 s before starting nginx so the first request doesn't 502 against a still-warming file-api.
  • exec nginx -g 'daemon off;' in foreground; tini is PID 1 and reaps both children.

.dockerignore

Scoped tightly to what the new image needs (packages/notes-app, packages/file-api, docker/). All upstream-Logseq dirs (src/, deps/, android/, ios/, fastlane/, resources/, etc.) and every node_modules/dist are excluded. Build context measured at ~1.5 MB.

.gitignore

Removed the inherited bare docker rule (from upstream Logseq) so the new docker/ directory at the repo root tracks correctly.

Bundle size estimate

Final runtime image:

  • node:22-alpine base ≈ 150 MB
  • nginx + tini ≈ 5 MB
  • file-api node_modules ≈ 15 MB
  • SPA dist/ ≈ 1 MB

Estimated ~170 MB compressed image. Acceptable; can be slimmed later with a leaner base or a static binary file-api if it matters.

How to deploy after merge

docker run \
  -v /host/path/to/brain:/brain \
  -p 8080:80 \
  ghcr.io/kelsi-bizer/bizeros-knowledge:latest

Open http://host:8080. The SPA lands on today's daily note, edits are persisted to /host/path/to/brain/daily/YYYY-MM-DD.md, and any other tool (CLI, agent, git) can read/write the same files directly.

Test plan

  • Build-Docker workflow completes on push to master
  • docker pull ghcr.io/kelsi-bizer/bizeros-knowledge:latest after merge
  • docker run -v $(pwd)/brain:/brain -p 8080:80 ghcr.io/kelsi-bizer/bizeros-knowledge:latest — SPA loads at localhost:8080
  • Type a note — file appears under ./brain/daily/YYYY-MM-DD.md
  • echo '# external' > ./brain/pages/external.md — sidebar refreshes via SSE
  • Cmd/Ctrl-click [[external]] — navigates to that file
  • docker stop returns within a few seconds (signal forwarding works)

Out of scope

  • Auth — anyone who can reach the port can read/write /brain. Phase 5.
  • Backups — no scheduled tar of /brain. Phase 5.
  • Resource limits — no USER directive (still runs as root inside container). Hardening for a follow-up.
  • README rewrite — still describes the old Logseq install path. Worth a separate cleanup PR.

Generated by Claude Code

The image now ships the post-Logseq stack: notes-app SPA served by
nginx, file-api sidecar on 127.0.0.1:3000, /brain mount for
markdown persistence. Pulls back from the upstream-Logseq build
that the image was inheriting.

Multi-stage Dockerfile:
- notes-app-builder: pnpm install + vite build -> dist
- file-api-builder: pnpm install --prod -> node_modules + src
- runtime (node:22-alpine + nginx + tini): SPA at
  /usr/share/nginx/html, file-api at /opt/file-api, started by
  /entrypoint.sh

docker/nginx.conf:
- /api/ proxied to 127.0.0.1:3000 with HTTP/1.1, no buffering, and
  24h read timeout so /api/watch SSE streams stay alive
- SPA fallback: try_files $uri $uri/ /index.html for hash routing
- client_max_body_size 25M to match file-api's MAX_BODY_BYTES default

docker/entrypoint.sh:
- mkdir -p $BRAIN_DIR (default /brain)
- start file-api in background; trap TERM/INT to forward shutdown
- poll /api/health for up to 5s before starting nginx so the first
  request doesn't 502
- exec nginx in foreground so PID 1 (tini) reaps it correctly

.dockerignore: scope context to docker/ + packages/notes-app +
packages/file-api; exclude all upstream-Logseq dirs and node_modules
trees. Build context is ~1.5 MB.

.gitignore: drop the bare 'docker' rule inherited from upstream so
the new docker/ directory at the repo root is tracked.

HEALTHCHECK: hit /api/health every 30s.

VOLUME ["/brain"], EXPOSE 80. Run with:
  docker run -v /host/brain:/brain -p 8080:80 \
    ghcr.io/kelsi-bizer/bizeros-knowledge:latest
@kelsi-bizer
kelsi-bizer marked this pull request as ready for review May 10, 2026 12:00
@kelsi-bizer
kelsi-bizer merged commit 916697a into master May 10, 2026
4 of 5 checks passed
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.

2 participants