Conversation
flamboh
added this pull request to stack #118
September 25, 2026 10:46
Run the SQLite dashboard as a Docker container on a self-hosted host, managed by the atlantis-campus Alchemy stack. The container publishes its port on the host loopback only; users connect through SSH port forwarding. The stack creates no Cloudflare resources and keeps Cloudflare only as the state store. - ATLANTIS_DB_DRIVER=sqlite builds use @sveltejs/adapter-node. - apps/web/Dockerfile builds with Bun and runs on Node 24.18.1, which avoids the Node 24.19+ better-sqlite3 GC abort (nodejs/node#65446). - The image tag is a content hash of the build inputs, so a source change replaces the container and an unchanged deploy is a no-op. - The data mount is writable because WAL-mode databases need their -shm/-wal sidecars; the dashboard still opens them read-only. - LOCAL_DATA_DIR selects the dataset directory the dashboard scans.
…chain - Rename the campus stack, scripts, env vars, and docs to self-hosted. - Hash only the Dockerfile.dockerignore allowlist instead of walking the whole checkout before applying ignores. - Emit `-p <port>` and quote arguments in the printed SSH tunnel command, and require an ssh:// Docker host URL. - Pin Bun 1.3.11 and Node 24.18.1 in package.json, .node-version files, shell.nix, and CI. - Add infra unit tests and run them in CI. - Lead the operations docs with setup steps; move internals to architecture docs and drop real host names.
Playwright 1.52 hangs while loading its config on Node 24.18.1. Keep shell.nix and the CI e2e job on the earlier Node 24 and document why; Bun stays pinned to 1.3.11 everywhere.
flamboh
force-pushed
the
stack/alchemy-campus
branch
from
September 27, 2026 04:48
e57ea21 to
ccb9892
Compare
This was referenced Sep 27, 2026
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.
Note
🤖 Claude Opus 5.5 on behalf of Oliver
ELI5
This packages the dashboard as a container that runs on a machine the team controls. People open it through an SSH tunnel from their own computer. Nothing is exposed to the network.
Why
The team needs a self-hosted dashboard that is separate from the dev and Cloudflare deployments. The pipeline already writes SQLite, so running the dashboard next to that data avoids loading it into D1.
Flows to exercise
Setup: a Docker host you can reach with
ssh <host> docker infowithout a password prompt, a data directory on it with<dataset-id>/netflow.sqlite, andCLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_IDexported (Alchemy stores state in Cloudflare and creates no Cloudflare resources).ssh <host> docker ps --filter name=atlantis-self-hosted-webshows the container ashealthy.ssh -N -L 8080:127.0.0.1:8080 user@host.example.com. With a port in the URL, it prints-p 2222 user@host.example.com. Openhttp://localhost:8080and the datasets from the data directory load.apps/web, the deploy replaces the container.bun run destroy:self-hosted --stage <name>removes the container, the current image, and the Docker context. The data directory is not touched.Decisions and edge cases
127.0.0.1:<port>on the host. There is no app auth or TLS, and access goes through SSH port forwarding. Users' SSH keys must allow port forwarding.ATLANTIS_SELF_HOSTED_DOCKER_HOSTmust be anssh://URL.ATLANTIS_SELF_HOSTED_DATA_DIRmust be an absolute path other than/.ATLANTIS_SELF_HOSTED_DATA_USERmust be<uid>:<gid>(default1000:1000).ATLANTIS_SELF_HOSTED_PORTdefaults to8080. Invalid values stop the deploy before anything changes.--stage alice_devfails before anything is created, so two spellings can never share a container or Docker context.-shm/-walfiles next to WAL-mode databases, even to read them. The app still opens every database read-only. The container runs as the data owner.Dockerfile.dockerignoreis an allowlist, and the hash walks only the allowlisted paths. Captures,target, andnode_modulesare never read, so an unreadable or huge directory elsewhere in the checkout cannot slow down or break a deploy. An ignore file that does not start with*is rejected.packageManager,shell.nix, the Dockerfile, CI). Node 24.18.1 is pinned in.node-versionfiles, the Dockerfile, and the CI web job, because 24.19+ aborts when better-sqlite3 statements are garbage-collected (nodejs/node#65446).shell.nixand the CI e2e job stay on an earlier Node 24: Playwright 1.52 hangs while loading its config on Node 24.18.1, and moving to Playwright 1.53+ also means moving the nixpkgs browser pin.1gwith a 768 MB heap.patches/alchemy@2.0.0-beta.79.patchadds onlymemorytoDocker.Container, because upstream has no memory option.scripts/netflow-db-docker.shon the host instead.paths.originorPROTOCOL_HEADERbefore adding any.Verification
Automated (CI and local):
format:check, lint and typecheck (web and infra),test:web, the newtest:infrasuite,build:web,test:e2e, landing lint, andbuild:landing. The infra tests cover tunnel-command generation (SSH aliases,user@host:port, IPv6, shell quoting, rejecting non-SSH URLs) and build-context hashing (allowlisted changes and build args change the hash; ignored subtrees and unreadable directories outside the allowlist don't). CI does not build or run the container.Manual (container, done before the rename and hashing changes): the image built on two Linux Docker hosts. A test stage served a CSV-derived dataset through
ssh -L. Pages and five API routes returned 200 and matched local SQLite byte for byte. The port was reachable only on the host's loopback (a request from another machine failed). An unchanged redeploy did nothing, and a source change replaced the container. Destroy left the host as it was and the stack state empty.Remaining manual verification: run flows 1–4 once more under the new
atlantis-self-hostedstack name. Include onessh://user@host:portURL to check the printed tunnel command.Claude Opus 5.5 · Claude Code (T3 Code)