156 lines
7.3 KiB
YAML
156 lines
7.3 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`. Every Jarvis image here tracks `stable` by
|
|
# default, so that is the whole upgrade. Postgres and Redis are not on a Jarvis channel — they follow
|
|
# their own upstream tags. Schema changes apply themselves when the api starts, and they are ONE-WAY:
|
|
# there is no migration history, so pulling an older api image does not put the schema back. Take a
|
|
# dump first. See the README.
|
|
#
|
|
# A channel tag does NOT mean the app forgets which build it is: `stable` is a second name on the same
|
|
# image as its version tag, and the version is stamped INTO the image when it is built. The footer in
|
|
# the app and /version.json keep reporting the real number whichever name you pulled it under — which
|
|
# is what lets you tell somebody which build you are on when something goes wrong.
|
|
#
|
|
# 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 (and JARVIS_IMAGE_AGENT, if
|
|
# you run the agent overlay) in .env to explicit version tags.
|
|
#
|
|
# No version number is written in this comment on purpose. Nothing in the publishing path would ever
|
|
# bump one, so a number here is a number that goes stale while nobody is looking.
|
|
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:stable}
|
|
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:-}
|
|
# Which distribution channel this instance follows, shown to signed-in operators beside the
|
|
# version numbers. Set `JARVIS_CHANNEL=stable` (or `dev`) in .env if you track a channel;
|
|
# LEAVE IT EMPTY IF YOU PIN EXACT VERSIONS, because then you follow no channel — you follow a
|
|
# decision — and the footer shows nothing rather than a label that stopped being true.
|
|
#
|
|
# It is not baked into the image, and cannot be: a channel is decided after a build and moves
|
|
# afterwards, so the same image is `dev` one week and `stable` the next. Only you know which
|
|
# one you are on.
|
|
APP_CHANNEL: ${JARVIS_CHANNEL:-}
|
|
|
|
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:stable}
|
|
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:
|