The stack now refuses to create new organizations, users or agents without `JARVIS_LICENSE_KEY`. Everything already set up keeps running, and an instance with no key contacts nobody at all — but a fresh install gets as far as its first administrator account and then needs a key, and that was documented nowhere here. The README now lists, in full and field by field, everything a licensed instance sends: counts and a version string, no names, no addresses, no conversation text, no asset inventory, no credentials. The only field that identifies your network rather than measuring something is the public URL, and it can be switched off.
139 lines
6.1 KiB
YAML
139 lines
6.1 KiB
YAML
# Jarvis — self-hosting stack.
|
|
#
|
|
# Everything runs from published images; nothing is built here and no source is needed.
|
|
#
|
|
# cp .env.example .env # then fill it in — see the comments in that file
|
|
# docker compose pull
|
|
# docker compose up -d
|
|
#
|
|
# Only the `web` service publishes a port. Its nginx serves the app and reverse-proxies /api and the
|
|
# websocket to the internal `api` service, so your own TLS terminator has exactly one target and the
|
|
# API is never reachable from outside this compose network.
|
|
#
|
|
# UPGRADING: `docker compose pull && docker compose up -d`. Both services track `latest` by default,
|
|
# so that is the whole upgrade. Schema changes apply themselves when the api starts.
|
|
#
|
|
# `latest` does NOT mean the app forgets which build it is: the tag is a second name on the same image
|
|
# as the version tag, and the version is stamped INTO the image at build time. The footer in the app and
|
|
# /version.json keep reporting 0.51.0 or whatever you actually pulled.
|
|
#
|
|
# To pin instead — recommended once you are in production, because it makes an upgrade a decision rather
|
|
# than a side effect of pulling — set JARVIS_IMAGE_API and JARVIS_IMAGE_WEB in .env to explicit tags.
|
|
name: jarvis
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: jarvis
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
|
POSTGRES_DB: jarvis
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U jarvis -d jarvis"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: unless-stopped
|
|
# Append-only so a restart does not lose the queue and the socket fan-out state.
|
|
command: ["redis-server", "--appendonly", "yes"]
|
|
volumes:
|
|
- redis_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
|
|
api:
|
|
image: ${JARVIS_IMAGE_API:-git.luxit.be/luxit/jarvis-api:latest}
|
|
restart: unless-stopped
|
|
# The assistant runs long operations, and a deploy is the most common thing that interrupts one.
|
|
# Given room to stop, the API aborts each loop, writes the partial answer with a note saying why
|
|
# the transcript ends there, and marks the run interrupted so the next process picks it up.
|
|
# Docker's 10s default is not enough. RUN_SHUTDOWN_GRACE_SEC must stay the smaller of the two.
|
|
stop_grace_period: 60s
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
API_PORT: "4000"
|
|
|
|
# The address a browser reaches Jarvis on. This is the CORS decision: requests from any other
|
|
# origin are refused, so a wrong value here presents as a sign-in page that cannot sign in.
|
|
WEB_ORIGIN: ${WEB_ORIGIN:?set WEB_ORIGIN in .env}
|
|
# The address baked into agent install commands and dialled by every enrolled machine. Usually
|
|
# the same string as WEB_ORIGIN; separate because they answer different questions, and a wrong
|
|
# value here does not fail at deploy time — it fails weeks later, on somebody else's server.
|
|
PUBLIC_URL: ${PUBLIC_URL:-${WEB_ORIGIN}}
|
|
|
|
DATABASE_URL: postgresql://jarvis:${POSTGRES_PASSWORD}@postgres:5432/jarvis?schema=public
|
|
REDIS_URL: redis://redis:6379
|
|
|
|
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?set JWT_ACCESS_SECRET in .env}
|
|
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:?set JWT_REFRESH_SECRET in .env}
|
|
JWT_ACCESS_TTL: "900"
|
|
JWT_REFRESH_TTL: "1209600"
|
|
|
|
# READ THE NOTE IN .env.example BEFORE CHANGING THIS. Every credential in the vault is
|
|
# encrypted under it; lose it and they are gone, with no recovery of any kind.
|
|
VAULT_MASTER_KEY: ${VAULT_MASTER_KEY:?set VAULT_MASTER_KEY in .env}
|
|
|
|
# Any OpenAI-compatible endpoint. Defaults to OpenAI itself.
|
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:?set OPENAI_API_KEY in .env}
|
|
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4o}
|
|
OPENAI_THINKING_LEVEL: ${OPENAI_THINKING_LEVEL:-medium}
|
|
|
|
# How many proxies sit in front and rewrite X-Forwarded-For. ONE is the web container's own
|
|
# nginx, which is always there — so 1 is right when nothing else fronts it, and 2 when your own
|
|
# TLS terminator does. Raising it is the dangerous direction: the API trusts that many hops of a
|
|
# header the client can forge, and too high lets a caller choose the IP that lands in the audit
|
|
# log, in the session list and in the enrollment rate limit.
|
|
TRUST_PROXY_HOPS: ${TRUST_PROXY_HOPS:-2}
|
|
|
|
# Required to create new organizations, users or agents. Without it an instance keeps running
|
|
# everything it already has, creates nothing new, and contacts nobody. See the README.
|
|
JARVIS_LICENSE_KEY: ${JARVIS_LICENSE_KEY:-}
|
|
|
|
AGENT_HEARTBEAT_INTERVAL_SEC: ${AGENT_HEARTBEAT_INTERVAL_SEC:-30}
|
|
RUN_SHUTDOWN_GRACE_SEC: ${RUN_SHUTDOWN_GRACE_SEC:-25}
|
|
|
|
# Where the agent binaries live, if you have them. Leaving this unset is a supported state:
|
|
# everything except the agent installer works, and the installer answers 503 saying no build is
|
|
# published. See the README — a self-hosted instance has no way to produce these.
|
|
AGENT_RELEASE_DIR: ${AGENT_RELEASE_DIR:-}
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- "fetch('http://localhost:4000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
# First boot syncs the schema and runs the data backfills before it listens.
|
|
start_period: 40s
|
|
|
|
web:
|
|
image: ${JARVIS_IMAGE_WEB:-git.luxit.be/luxit/jarvis-web:latest}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
ports:
|
|
# Put your own TLS terminator in front of this. Jarvis speaks plain HTTP here on purpose and
|
|
# reads X-Forwarded-Proto to know what the browser actually used.
|
|
- "${JARVIS_PORT:-8080}:80"
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|