Sync the self-hosting stack

This commit is contained in:
2026-08-31 21:43:38 +02:00
parent 70db57d367
commit fdd35f2d27
5 changed files with 352 additions and 0 deletions
+63
View File
@@ -215,10 +215,20 @@ services:
# answer you want from an upgrade that was not expecting to lose anything.
SCHEMA_ACCEPT_DATA_LOSS: ${SCHEMA_ACCEPT_DATA_LOSS:-false}
# The browser the assistant may use, if you run one. Unset is a supported state: the two web
# tools report themselves unavailable and everything else works. It is a separate service ON
# PURPOSE — a browser runs code written by strangers, and this container holds the vault key.
# Empty by default, matching the profile above: with no browser running, pointing the api at
# one would only produce timeouts. Set it when you enable the profile.
BROWSER_URL: ${BROWSER_URL:-}
# 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:-}
networks:
- default
- browsernet
healthcheck:
test:
- CMD
@@ -231,6 +241,49 @@ services:
# First boot syncs the schema and runs the data backfills before it listens.
start_period: 40s
# The headless browser, and everything about this service is about keeping it away from the rest.
#
# WHY IT IS NOT PART OF THE API. A browser's whole job is to fetch code written by strangers and
# run it. The api container holds VAULT_MASTER_KEY and a live connection to Postgres and Redis, so
# a browser sharing its network would let any page the assistant is pointed at reach them — and
# reach a cloud metadata endpoint, which is how instance credentials get read out loud.
#
# THE ISOLATION IS THE NETWORK, not the good intentions. This service is on `browsernet` and
# NOTHING ELSE. postgres and redis are on `default`, which it is not a member of, so it cannot
# resolve or reach them at all. The api is on both, because it is the only thing that calls this.
# It also carries no secret, no volume and no database URL: a renderer that escapes lands in a
# container that holds nothing worth having.
#
# Delete this service and unset BROWSER_URL if you would rather Jarvis had no web access. Nothing
# else stops working; the two web tools report themselves unavailable.
browser:
image: ${JARVIS_IMAGE_BROWSER:-git.luxit.be/luxit/jarvis-browser:stable}
# OPT-IN. A profiled service does not start unless you ask for it:
# COMPOSE_PROFILES=browser docker compose up -d
# Web access is a decision, not a default — an assistant that can read the internet is a new
# class of input into a system that administers your customers, so you turn it on deliberately.
profiles:
- browser
restart: unless-stopped
networks:
- browsernet
# A renderer is a memory hog, and a compromised one is a fork bomb. Both are bounded here rather
# than left to take the host down with them.
mem_limit: 1g
pids_limit: 512
security_opt:
- no-new-privileges:true
healthcheck:
test:
- CMD
- node
- -e
- "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
web:
image: ${JARVIS_IMAGE_WEB:-git.luxit.be/luxit/jarvis-web:stable}
restart: unless-stopped
@@ -247,3 +300,13 @@ volumes:
# The keys, deliberately apart from postgres_data. Back it up, and not to the same place: a
# database dump is worthless to a thief without this, and worthless to YOU without it either.
jarvis_secrets:
# Two networks, and the split is a security control rather than tidiness.
#
# default is what every service was already on implicitly — postgres, redis, api, web.
# browsernet exists so the browser can be reached by the api and reach the internet, WITHOUT
# being able to resolve or connect to the database and the cache. A service that names no
# network is on default, so nothing changes for the ones that were already there.
networks:
default:
browsernet: