Phase 3: replace docker image with notes-app + file-api bundle - #8
Merged
Conversation
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
marked this pull request as ready for review
May 10, 2026 12:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-Dockerwill publish a workingghcr.io/kelsi-bizer/bizeros-knowledge:latestthat does what BizerOS Knowledge is supposed to do.What changed
Dockerfile— full replacement, three stagesnotes-app-builder(node:22-alpine+ pnpm) — installspackages/notes-appdeps from the lockfile, runsvite build, emitsdist/.file-api-builder— installspackages/file-apiprod deps only, copiessrc/.node:22-alpine+nginx+tini. SPA bundle lands at/usr/share/nginx/html; file-api at/opt/file-api; nginx config + entrypoint copied in.HEALTHCHECKhits/api/healthevery 30s.VOLUME ["/brain"]andENV BRAIN_DIR=/brainmake the persistence point obvious.docker/nginx.conf/api/proxied to127.0.0.1:3000(the file-api). Configured for SSE:proxy_http_version 1.1,proxy_buffering off,proxy_read_timeout 24h, emptyConnectionheader —/api/watchstreams stay alive.try_files $uri $uri/ /index.htmlso deep links and hash routes resolve.client_max_body_size 25Mmatches the file-api's defaultMAX_BODY_BYTES.docker/entrypoint.shmkdir -p $BRAIN_DIR(default/brain) so the file-api watcher and tree endpoint don't ENOENT on first run.node /opt/file-api/src/server.jsin the background; trapsTERM/INTand forwards them./api/healthfor 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;tiniis PID 1 and reaps both children..dockerignoreScoped 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 everynode_modules/distare excluded. Build context measured at ~1.5 MB..gitignoreRemoved the inherited bare
dockerrule (from upstream Logseq) so the newdocker/directory at the repo root tracks correctly.Bundle size estimate
Final runtime image:
node:22-alpinebase ≈ 150 MBnginx+tini≈ 5 MBfile-apinode_modules≈ 15 MBdist/≈ 1 MBEstimated ~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
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-Dockerworkflow completes on push to masterdocker pull ghcr.io/kelsi-bizer/bizeros-knowledge:latestafter mergedocker run -v $(pwd)/brain:/brain -p 8080:80 ghcr.io/kelsi-bizer/bizeros-knowledge:latest— SPA loads atlocalhost:8080./brain/daily/YYYY-MM-DD.mdecho '# external' > ./brain/pages/external.md— sidebar refreshes via SSE[[external]]— navigates to that filedocker stopreturns within a few seconds (signal forwarding works)Out of scope
/brain. Phase 5./brain. Phase 5.USERdirective (still runs as root inside container). Hardening for a follow-up.Generated by Claude Code