You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[idea] Docker 05/14: First CPU image and compose, without local model runtimes #2105
docker/local/Dockerfile, context surfsense_local/, Ubuntu 24.04: frontend built with VITE_API_BASE=/api, backend uv sync into a venv, the bundled models (bge-small, the Docling pack), the headless entry, Caddy, tini.
docker/local/Dockerfile.dockerignore.
docker/local/caddy/Caddyfile and caddy/access-local.caddy: serve the frontend, proxy /api/* with the prefix stripped, refuse /api/internal/*, answer only localhost, 127.0.0.1 and [::1].
electron/src/headless/caddy.ts: Caddy as one more SidecarSpec, started after /health.
docker/local/docker-compose.yml and .env.example: one service, port 127.0.0.1:3929, a /data volume. SURFSENSE_LOCAL_CORS_ORIGINS set empty.
A HEALTHCHECK on /health. Nothing outside docker/local/ in docker/ changes.
Done when
docker compose -f docker/local/docker-compose.yml up --build, then at http://localhost:3929: upload a PDF, it reaches ready, and a chat through a remote connection answers with citations.
Restarting the container keeps the data.
A request with Host: 192.168.1.10 is refused.
docker stop shuts down cleanly within the timeout.
Scope: code and tests only. Don't edit docs/ or AGENTS.md, except to fix a link this change breaks; the maintainers update the docs when the Docker work ships.
Why would it help?
Each step leaves something that runs, so the Docker image is built and reviewed one piece at a time.
I would like to take this step once #2104 is merged. #2103 is with @llwand1 (#2215) and #2104 with @Protocol-zero-0, so I will not start on the headless side before theirs lands; caddy.ts will use whatever SidecarSpec and boot context #2104 ends with.
What I would deliver, following the proposal's Files section:
docker/local/Dockerfile (context surfsense_local/, Ubuntu 24.04; stages frontend with VITE_API_BASE=/api, backend with uv sync, models for bge-small and the Docling pack through the desktop's fetch scripts, runtime with Node, Caddy and tini), Dockerfile.dockerignore, and a HEALTHCHECK on /health.
caddy/Caddyfile and caddy/access-local.caddy: the frontend, /api/* with the prefix stripped, /api/internal/* refused, and only localhost, 127.0.0.1 and [::1] answered.
electron/src/headless/caddy.ts with node:test tests beside it, started after /health.
docker-compose.yml and .env.example: one service on 127.0.0.1:3929, the /data volume, SURFSENSE_LOCAL_CORS_ORIGINS empty.
How I would check the four boxes: on Docker on an Apple Silicon Mac, so the image I run is linux/arm64. I can build linux/amd64 under emulation to see that it builds, but I would not call it tested until step 6's CI runs it. For the chat box I would add an OpenAI-compatible connection pointing at a server on the host, and say in the PR which one and which model. The Host: 192.168.1.10 refusal, data surviving a restart and the docker stop time I would show with the commands and their output.
Until #2104 merges I can prepare the parts that do not depend on it (the Dockerfile stages, the Caddy files with a check of the host rule against a stub upstream) and keep them local.
While waiting for #2104 I built the three stages that do not depend on it (frontend, backend, models) on dev at 52f51cb, as linux/arm64, to see what the step will meet. Two things are worth knowing before anyone writes the Dockerfile; neither is in the proposal.
1. The backend no longer builds from the surfsense_local/ context alone.backend/pyproject.toml takes surfsense-plugin-manifest by path from ../../plugins/bundles/core/manifest (since the plugins work), which is outside the context the proposal names. uv sync --frozen stops with Distribution not found at: file:///plugins/bundles/core/manifest.
What worked here, keeping surfsense_local/ as the context: pass plugins/ as a second, named build context and install the package into the venv as a built package, so the runtime stage needs the venv and not that directory.
built with --build-context plugins=plugins (Compose: additional_contexts). The other way is to make the repository root the context. That is your call, since it changes a line of the proposal; I would take the named context.
2. A minimal Ubuntu 24.04 needs three packages for the parser. Docling's OCR brings opencv-python, and import cv2 fails without libxcb1, libgl1 and libglib2.0-0t64 (found by adding them one at a time until the import worked). Without them scripts/fetch_docling_models.py fails in the models stage, and ingest would fail the same way at run time.
With both in place, on arm64:
stage
result
frontend
pnpm build with VITE_API_BASE=/api: 20 MB of dist
backend
uv sync from the lock: torch 2.14.0+cpu, the venv is 1.5 GB, import api.main works
models
bge-small and the Docling pack by the desktop's two fetch scripts: 462 MB, missing_parser_folders() returns []
Not done: linux/amd64, the runtime stage, Caddy inside the image, and anything that needs the headless entry. One thing about my network and not the image: the campus resolver here does not resolve cdn-lfs-cn-1.modelscope.cn, where Docling's downloader gets the RapidOCR weights, so I pinned that host for my build. CI will tell whether a runner sees it.
Thanks for the arm64 build notes. I checked the backend pyproject and confirmed that surfsense-plugin-manifest points outside the proposed surfsense_local/ context. I have recorded that build-context dependency and your reported Ubuntu parser libraries for the image acceptance checks.
For #2104, I am following the shared boot interface now on dev: it returns apiUrl, waitHealthy(), the sidecar handles and stopWatching(). That gives #2105 a concrete handoff: wire Caddy to the returned API URL after the health gate, while the headless entry handles watcher/child cleanup on shutdown. Docker packaging and Caddy stay in your #2105 scope. I will link the #2104 implementation when it is ready; this note does not claim that I have reproduced the image build.
Activity
ybai08 commentedon Oct 10, 2026
I would like to take this step once #2104 is merged. #2103 is with @llwand1 (#2215) and #2104 with @Protocol-zero-0, so I will not start on the headless side before theirs lands;
caddy.tswill use whateverSidecarSpecand boot context #2104 ends with.What I would deliver, following the proposal's Files section:
docker/local/Dockerfile(contextsurfsense_local/, Ubuntu 24.04; stagesfrontendwithVITE_API_BASE=/api,backendwithuv sync,modelsfor bge-small and the Docling pack through the desktop's fetch scripts,runtimewith Node, Caddy andtini),Dockerfile.dockerignore, and aHEALTHCHECKon/health.caddy/Caddyfileandcaddy/access-local.caddy: the frontend,/api/*with the prefix stripped,/api/internal/*refused, and onlylocalhost,127.0.0.1and[::1]answered.electron/src/headless/caddy.tswithnode:testtests beside it, started after/health.docker-compose.ymland.env.example: one service on127.0.0.1:3929, the/datavolume,SURFSENSE_LOCAL_CORS_ORIGINSempty.How I would check the four boxes: on Docker on an Apple Silicon Mac, so the image I run is
linux/arm64. I can buildlinux/amd64under emulation to see that it builds, but I would not call it tested until step 6's CI runs it. For the chat box I would add an OpenAI-compatible connection pointing at a server on the host, and say in the PR which one and which model. TheHost: 192.168.1.10refusal, data surviving a restart and thedocker stoptime I would show with the commands and their output.Until #2104 merges I can prepare the parts that do not depend on it (the Dockerfile stages, the Caddy files with a check of the host rule against a stub upstream) and keep them local.
ybai08 commentedon Oct 11, 2026
While waiting for #2104 I built the three stages that do not depend on it (
frontend,backend,models) ondevat 52f51cb, aslinux/arm64, to see what the step will meet. Two things are worth knowing before anyone writes the Dockerfile; neither is in the proposal.1. The backend no longer builds from the
surfsense_local/context alone.backend/pyproject.tomltakessurfsense-plugin-manifestby path from../../plugins/bundles/core/manifest(since the plugins work), which is outside the context the proposal names.uv sync --frozenstops withDistribution not found at: file:///plugins/bundles/core/manifest.What worked here, keeping
surfsense_local/as the context: passplugins/as a second, named build context and install the package into the venv as a built package, so the runtime stage needs the venv and not that directory.built with
--build-context plugins=plugins(Compose:additional_contexts). The other way is to make the repository root the context. That is your call, since it changes a line of the proposal; I would take the named context.2. A minimal Ubuntu 24.04 needs three packages for the parser. Docling's OCR brings
opencv-python, andimport cv2fails withoutlibxcb1,libgl1andlibglib2.0-0t64(found by adding them one at a time until the import worked). Without themscripts/fetch_docling_models.pyfails in themodelsstage, and ingest would fail the same way at run time.With both in place, on arm64:
frontendpnpm buildwithVITE_API_BASE=/api: 20 MB ofdistbackenduv syncfrom the lock: torch2.14.0+cpu, the venv is 1.5 GB,import api.mainworksmodelsmissing_parser_folders()returns[]Not done:
linux/amd64, the runtime stage, Caddy inside the image, and anything that needs the headless entry. One thing about my network and not the image: the campus resolver here does not resolvecdn-lfs-cn-1.modelscope.cn, where Docling's downloader gets the RapidOCR weights, so I pinned that host for my build. CI will tell whether a runner sees it.Protocol-zero-0 commentedon Oct 12, 2026
Thanks for the arm64 build notes. I checked the backend pyproject and confirmed that
surfsense-plugin-manifestpoints outside the proposedsurfsense_local/context. I have recorded that build-context dependency and your reported Ubuntu parser libraries for the image acceptance checks.For #2104, I am following the shared boot interface now on dev: it returns
apiUrl,waitHealthy(), the sidecar handles andstopWatching(). That gives #2105 a concrete handoff: wire Caddy to the returned API URL after the health gate, while the headless entry handles watcher/child cleanup on shutdown. Docker packaging and Caddy stay in your #2105 scope. I will link the #2104 implementation when it is ready; this note does not claim that I have reproduced the image build.