Self-hosting¶
Container¶
The repository's Dockerfile builds one image with the web app, the API, the docs site and the CLI:
docker build -t corpusos .
docker run --rm -p 8080:8080 -v "$PWD/data:/data" \
-e CORPUSOS_WEB_AUTH=firebase -e CORPUSOS_ADMINS=me@example.com \
-e CORPUSOS_FIREBASE_PROJECT_ID=my-project -e CORPUSOS_FIREBASE_API_KEY=... \
-e CORPUSOS_FIREBASE_AUTH_DOMAIN=my-project.firebaseapp.com \
-e GOOGLE_APPLICATION_CREDENTIALS=/data/sa.json corpusos
With no arguments, the image creates the corpus at /data/corpus on first start (owned by CORPUS_OWNERS, default CORPUSOS_ADMINS) and serves the web app on 0.0.0.0:$PORT. Any other arguments run as a corpusos command, for example docker run … corpusos status -C /data/corpus. Off loopback the header mode is refused, so choose firebase or jwt (Web app and API). For a quick local trial without sign-in, use corpusos web outside a container.
| Variable | Meaning |
|---|---|
CORPUSOS_WEB_AUTH |
firebase, jwt or header |
CORPUSOS_PUBLIC_URL |
the https URL users open |
CORPUSOS_ADMINS |
comma-separated admin emails |
CORPUSOS_ALLOWED_EMAILS, CORPUSOS_ALLOWED_DOMAINS |
who may sign in |
CORPUSOS_FIREBASE_PROJECT_ID, CORPUSOS_FIREBASE_API_KEY, CORPUSOS_FIREBASE_AUTH_DOMAIN |
the Firebase web config (public values) |
CORPUSOS_JWT_* |
HEADER, ISSUER, AUDIENCE, JWKS_URL for proxy sign-in |
CORPUSOS_STORAGE_DIR |
the storage folder (default <corpus>/storage) |
CORPUSOS_CATALOG_PATH |
a local-disk catalog when the corpus is on network storage |
CORPUSOS_SYNC_INTERVAL_MINUTES |
sync storage on a timer (0 = off) |
CORPUSOS_JOBS |
background (default) or inline: run ingest inside the request, for hosts that only give CPU during requests |
CORPUS_OWNERS |
the default access list for a new corpus (--owner) |
Secrets never go into config files. Put them in environment variables or your secret manager, and reference them as ${VAR}.
Google Cloud Run (example)¶
Two shapes:
| Free tier (scale to zero) | Always on | |
|---|---|---|
| Instances | --min-instances 0 --max-instances 1 |
--min-instances 1 --max-instances 1 --no-cpu-throttling |
| Ingest | CORPUSOS_JOBS=inline: the upload or sync request waits for it |
background jobs, plus the CORPUSOS_SYNC_INTERVAL_MINUTES timer |
| Files copied into the bucket | picked up when someone presses Sync (or calls POST /api/sync) |
also on the timer |
| Cost | within Cloud Run's free tier for light use; the first request after idle starts the app (a few seconds) | one instance billed around the clock |
Either way the catalog is snapshotted after every write and on shutdown.
- Storage: a Cloud Storage bucket mounted as a volume at
/data. The corpus goes in/data/corpus, storage in/data/corpus/storage, andCORPUSOS_CATALOG_PATH=/tmp/catalog/catalog.db. - Instances: exactly one (
--max-instances 1 --min-instances 1) with--no-cpu-throttling, so ingest jobs and the sync timer keep running between requests. - Sign-in:
firebase(the service runs public, and the app enforces sign-in), or IAP withjwt. - Files placed in the bucket directly (
gcloud storage cp report.pdf gs://BUCKET/corpus/storage/finance/) are picked up by the next sync.
gcloud run deploy corpusos --image REGION-docker.pkg.dev/PROJECT/REPO/corpusos:TAG \
--region us-central1 --max-instances 1 --min-instances 1 --no-cpu-throttling --memory 2Gi \
--execution-environment gen2 --add-volume name=data,type=cloud-storage,bucket=BUCKET \
--add-volume-mount volume=data,mount-path=/data \
--set-env-vars CORPUSOS_WEB_AUTH=firebase,CORPUSOS_CATALOG_PATH=/tmp/catalog/catalog.db,... \
--allow-unauthenticated
Backups¶
The corpus tree is the backup. It holds the raw bytes, every revision, and a catalog snapshot after each write. Turn on object versioning on the bucket, or snapshot the volume.