Nexara Connect docs0.1.0

Self-hosting

Nexara Connect ships as one container with one volume. There is no external database, queue or cache.

Requirements

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).

  1. Create an application from the git repository (private repos: a deploy key), build pack Dockerfile, port 8080.
  2. Add a persistent storage mounted at /data.
  3. Set the environment variables below. At minimum MASTER_KEY, PUBLIC_URL, OWNER_EMAIL, OWNER_PASSWORD.
  4. Add the domain, keep the generated sslip.io domain 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:

VariableDefaultMeaning
PORT8080Listen 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_URLderived from the requestExternal origin. Used for OAuth metadata, the MCP resource URI and invite links. Set it in production
WORKSPACEmainId of the workspace created for the first owner
OWNER_EMAILnoneFirst boot only, and only while no human exists: creates the owner
OWNER_PASSWORDnonePassword for that owner. Change it in the app afterwards; the variable is ignored once a human exists
MASTER_KEYgenerated into $DATA_DIR/master.keyRoot secret. HKDF derives the token pepper, cookie keys and per-workspace seal keys. Losing it makes sealed secrets unreadable and invalidates every token
SIGNUPSopenopen lets anyone create an account and their own workspace. invite allows sign-up only with an invite
REQUIRE_TOTPfalsetrue forces owners to enrol TOTP at sign-in
ALLOWED_HOSTSempty (allow all)Comma list of Host names accepted on /mcp (DNS rebinding guard). localhost and 127.0.0.1 are always allowed
OAUTH_EXTRA_REDIRECTSemptyComma list of extra exact OAuth redirect URIs allowed at client registration
EMBEDDINGSonoff 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_MODEdryNightly 03:00 UTC job. dry lists archive candidates, apply archives them
WEB_DISTweb/distFolder with the built web app. Without it / serves a small status page
APP_VERSION0.1.0Version reported by /healthz, /readyz and MCP
GIT_SHASOURCE_COMMIT, else devCommit reported by /readyz. The image sets it from the GIT_SHA build argument, else the SOURCE_COMMIT build argument (Coolify)
TRUST_PROXYNODE_ENV=productionTrust 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

  1. Back up (above).
  2. Pull the new code and rebuild the image, or redeploy in Coolify.
  3. Start it. Numbered core.db migrations in server/src/db.ts run at boot, and each workspace index is re-synced from git (hash-incremental) when the workspace opens.
  4. Check GET /readyz shows the new git_sha, then run scripts/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:

CommandWhat 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.jsRebuild every workspace index from git
node server/dist/cli/verify-audit.jsVerify the audit hash chain
Generated from self-hosting.md by docs/site/build.mjs. Edit the Markdown, then rebuild.