Self-hosting
Nexara Connect ships as one container with one volume. There is no external database, queue or cache.
Requirements
- Docker (or any OCI runtime), 1 GB RAM for the container (more if embeddings are on), a few GB of disk for
/data. - A public HTTPS origin if you want Claude.ai or ChatGPT to connect. Terminate TLS in a reverse proxy (Traefik, Caddy, nginx) that forwards to port 8080.
Build and run with Docker
git clone https://github.com/Analytica-Info/nexara-connect.git && cd nexara-connect
docker build --build-arg GIT_SHA=$(git rev-parse --short HEAD) -t nexara .
docker run -d --name nexara --restart unless-stopped -p 8080:8080 \
-v nexara-data:/data \
-e MASTER_KEY=... -e OWNER_EMAIL=you@example.com -e OWNER_PASSWORD='...' \
-e PUBLIC_URL=https://context.example.com \
-e SIGNUPS=invite -e REQUIRE_TOTP=true -e ALLOWED_HOSTS=context.example.com \
nexara
The image is multi-stage: the build stage installs dev dependencies and builds server/ with tsc and web/ with Vite; the runtime stage is node:22-bookworm-slim with git and tini, runs as the node user and has a HEALTHCHECK on /healthz. entrypoint.sh creates /data/ws and /data/backups and fails fast if /data is not writable.
Build and run with Coolify
This is how staging and production run (see docs/DEPLOY.md for the real app ids).
- Create an application from the git repository (private repos: a deploy key), build pack Dockerfile, port 8080.
- Add a persistent storage mounted at
/data. - Set the environment variables below. At minimum
MASTER_KEY,PUBLIC_URL,OWNER_EMAIL,OWNER_PASSWORD. - Add the domain, keep the generated
sslip.iodomain as a fallback until DNS resolves, and deploy.
Coolify injects NODE_ENV=production into the build; the Dockerfile sets NODE_ENV=development in the build stage so dev dependencies are still installed. Coolify passes SOURCE_COMMIT as a build argument (turn on Include Source Commit in Build in the app's General settings if /readyz still says dev); it is not in the runtime environment. The final Dockerfile stage declares ARG GIT_SHA and ARG SOURCE_COMMIT and defaults GIT_SHA to SOURCE_COMMIT before ENV GIT_SHA=${GIT_SHA}, so the running server sees the commit and /readyz reports it as git_sha.
Environment variables
Every variable read by server/src/config.ts:
| Variable | Default | Meaning |
|---|---|---|
PORT | 8080 | Listen port on 0.0.0.0 |
DATA_DIR | ./data (/data in the image) | Holds core.db, master.key if generated, models/, and ws/<id>/repo plus ws/<id>/index.db |
PUBLIC_URL | derived from the request | External origin. Used for OAuth metadata, the MCP resource URI and invite links. Set it in production |
WORKSPACE | main | Id of the workspace created for the first owner |
OWNER_EMAIL | none | First boot only, and only while no human exists: creates the owner |
OWNER_PASSWORD | none | Password for that owner. Change it in the app afterwards; the variable is ignored once a human exists |
MASTER_KEY | generated into $DATA_DIR/master.key | Root secret. HKDF derives the token pepper, cookie keys and per-workspace seal keys. Losing it makes sealed secrets unreadable and invalidates every token |
SIGNUPS | open | open lets anyone create an account and their own workspace. invite allows sign-up only with an invite |
REQUIRE_TOTP | false | true forces owners to enrol TOTP at sign-in |
ALLOWED_HOSTS | empty (allow all) | Comma list of Host names accepted on /mcp (DNS rebinding guard). localhost and 127.0.0.1 are always allowed |
OAUTH_EXTRA_REDIRECTS | empty | Comma list of extra exact OAuth redirect URIs allowed at client registration |
EMBEDDINGS | on | off uses keyword search only. on loads Xenova/bge-small-en-v1.5 in a worker thread and falls back to keyword search if it cannot load |
LIFECYCLE_MODE | dry | Nightly 03:00 UTC job. dry lists archive candidates, apply archives them |
WEB_DIST | web/dist | Folder with the built web app. Without it / serves a small status page |
APP_VERSION | 0.1.0 | Version reported by /healthz, /readyz and MCP |
GIT_SHA | SOURCE_COMMIT, else dev | Commit reported by /readyz. The image sets it from the GIT_SHA build argument, else the SOURCE_COMMIT build argument (Coolify) |
TRUST_PROXY | NODE_ENV=production | Trust X-Forwarded-* from the rightmost hop only (H3). Set false to disable on an untrusted network |
Recommended production values: SIGNUPS=invite, REQUIRE_TOTP=true, ALLOWED_HOSTS=<your host>, PUBLIC_URL=https://<your host>, an explicit MASTER_KEY.
The volume
/data/core.db identity, grants, tokens, sessions, proposals, threads, audit (NOT rebuildable)
/data/master.key only if MASTER_KEY was not set
/data/ws/<id>/repo/ the workspace git repository (the content)
/data/ws/<id>/index.db search index (rebuildable with npm run reindex)
/data/models/ downloaded embedding model
/data/backups/ created by the entrypoint, not yet used
Backups
TODO: automated backups are not implemented in 0.1.0. Until they are, back up by hand, with the container stopped or using SQLite's online backup:
docker exec nexara sh -c 'cd /data && for d in ws/*/repo; do git -C "$d" bundle create "/data/backups/$(basename $(dirname $d)).bundle" --all; done'
docker exec nexara node -e "const D=require('better-sqlite3');new D('/data/core.db').backup('/data/backups/core.db').then(()=>console.log('ok'))"
docker cp nexara:/data/backups ./nexara-backup-$(date +%F)
Keep MASTER_KEY separately from the backups. index.db does not need backing up. The planned design (Litestream for core.db, nightly encrypted git bundles, weekly restore drill) is in docs/SPEC-v2.md section 9.
Upgrade
- Back up (above).
- Pull the new code and rebuild the image, or redeploy in Coolify.
- Start it. Numbered
core.dbmigrations inserver/src/db.tsrun at boot, and each workspace index is re-synced from git (hash-incremental) when the workspace opens. - Check
GET /readyzshows the newgit_sha, then runscripts/smoke.sh <url> <email> <password>.
Rollback: redeploy the previous commit. The content repository is forward compatible (plain markdown), but restore core.db from the backup if the newer version changed its schema.
Maintenance commands
Run inside the container (docker exec nexara ...) or from a checkout with the same DATA_DIR:
| Command | What it does |
|---|---|
node server/dist/cli/import.js <folder> --space <name> | Import a folder of markdown (npm run import -- in a checkout) |
node server/dist/cli/reindex.js | Rebuild every workspace index from git |
node server/dist/cli/verify-audit.js | Verify the audit hash chain |