antoine 041fa054b3 Jarvis needs a licence key, and this says what a licensed instance reports
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.
2026-08-18 20:00:48 +02:00

Self-hosting Jarvis

Jarvis is an AI-assisted infrastructure administration platform for MSPs. This repository runs it from published container images — no source, no build, no account with the project.

What you need

  • Docker with Compose v2, on anything Linux.
  • A hostname and a TLS terminator in front of it. Jarvis speaks plain HTTP and reads X-Forwarded-Proto; it does not manage certificates.
  • An API key for an OpenAI-compatible endpoint.
  • Roughly 2 GB of RAM for the stack and room for Postgres to grow.

Install

git clone https://git.luxit.be/Luxit/jarvis-selfhost.git
cd jarvis-selfhost
cp .env.example .env
$EDITOR .env          # every comment in it is load-bearing; the secrets note especially
docker compose pull
docker compose up -d

First boot syncs the database schema and runs its data backfills before the API listens, so give it about a minute. Then point your reverse proxy at JARVIS_PORT and open the app.

The first account created becomes super-admin. Self-registration is open by default, so sign up immediately after the stack is up and then close registration under Settings → Platform → General. Leaving it open means anyone who reaches the sign-in page gets an account.

Your reverse proxy has two requirements

Both are the kind that produce confusing symptoms rather than clean errors.

  • Forward the WebSocket upgrade. Without it the chat cannot stream and no agent can connect.
  • Give it a long read timeout — 300s or so. A reasoning model can go 90+ seconds without emitting a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather than a timeout, which reads like a bug in Jarvis.

Upgrading

docker compose pull && docker compose up -d

That is the whole upgrade: both services track latest. Schema changes apply themselves when the api starts, and the api and the web are versioned independently — their numbers are not meant to match, because usually only one side changed.

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 when it is built — so the footer in the app and /version.json keep reporting the real number, whichever name you pulled it under. That is what lets you tell somebody which build you are on when something goes wrong.

Once you are in production, consider pinning: set JARVIS_IMAGE_API and JARVIS_IMAGE_WEB in .env to explicit tags. It makes an upgrade a decision rather than a side effect of pulling.

Take a database dump before an upgrade that moves the api's minor version:

docker compose exec -T postgres pg_dump -U jarvis jarvis | gzip > jarvis-$(date +%F).sql.gz

What you get, and what you do not

Working: the assistant with its tool-calling loop, plan-level approvals for destructive operations, the encrypted vault, the asset registry, multi-tenant RBAC, real-time conversations, documents and exports, the audit trail, and the connectors — SSH, Proxmox, Microsoft 365 and MikroTik.

The Jarvis agent is an optional overlay, off by default. Enrolling a machine downloads a compiled binary that the api serves from AGENT_RELEASE_DIR, and a compose-only deployment has no way to produce one. docker-compose.agent.yml supplies it as a pullable image instead. Leaving it off is a supported state rather than a broken one: everything else works, the installer answers 503 saying no build is published, and the connectors above reach machines without it.

The agent

Turn it on by adding one line to .env, so that every later docker compose command picks up both files with no extra flags:

COMPOSE_FILE=docker-compose.yml:docker-compose.agent.yml

then docker compose pull && docker compose up -d. A one-shot agent-releases service copies the release into a volume the api reads, and exits. From there, Settings → Agents hands you the install one-liner for each platform.

Three things worth knowing about it:

  • Until that publisher exits cleanly, the api does not start. That is deliberate — a release that failed to arrive should stop the deploy loudly rather than leave you handing 404s to every installer you run this week. The cost is that an unreachable registry blocks the whole stack. The comment in the file names the three lines to drop if you would rather it degraded quietly.
  • Upgrading it does not restart anything. The api computes each download's checksum from the bytes on disk on every request, so a new release in the volume is served immediately.
  • The agent version is its own number. It moves independently of the api and the web, and a Jarvis release usually does not touch it at all. Pin it with JARVIS_IMAGE_AGENT when you pin the others.

The agent runs as root on Linux and macOS and as LocalSystem on Windows, deliberately — its purpose is to administer the machine. What it may actually do is decided by Jarvis' autonomy policy and its approval gates, not by the account it runs under. Read that section of the main documentation before enrolling anything you care about.

Things worth knowing before you trust it with production

These are deliberate and documented rather than surprises waiting to be found.

  • VAULT_MASTER_KEY has no recovery. Read the note in .env.example. A database backup does not protect the vault — the backup holds ciphertext encrypted under that key.
  • SSH host keys are not verified. Every SSH connection trusts whatever key answers. This is the one gap in the execution path with no compensating control.
  • Access tokens cannot be revoked. Revoking a session or changing a password invalidates refresh tokens; a stolen access token stays valid for up to 15 minutes.
  • The api must run as a single replica. In-flight runs, pending approvals and presence live in per-process memory. Scaling it out silently drops cancels and approvals answered on the wrong one.
  • Both containers run as root, and /api/health answers 200 with status: "degraded" when the database is unreachable, so the container healthcheck alone is not a liveness signal for the DB.
  • The assistant executes real operations on real infrastructure. Destructive ones require in-chat human approval; mutating ones do not. Decide your autonomy level per conversation accordingly.

Backups

Postgres holds everything except the vault key. Two volumes matter:

docker compose exec -T postgres pg_dump -U jarvis jarvis | gzip > jarvis.sql.gz

Plus VAULT_MASTER_KEY, stored somewhere that is not this host. A dump without the key is a database whose credentials cannot be read.

Licence keys, and what your instance reports

Jarvis needs a licence key. Ask your provider for one and put it in .env as JARVIS_LICENSE_KEY. The key is a signed token your instance verifies offline — it carries your term and your limits, and it needs no network to be checked.

Without one, an instance keeps running everything already set up — every organization, every user, every agent, and the assistant itself — and refuses to create anything NEW. So an existing deployment does not stop working when this reaches it, and a fresh install gets as far as its first administrator account and then needs a key.

An unlicensed instance contacts nobody. No check-in, no telemetry, nothing leaves your network at all. The reporting below starts only once a key is in place.

A licensed instance then reports to the address written into that key, by default once a day. This is everything it sends, in full:

Field What it is
Licence id Which licence this is
Instance id + public key A key pair your instance generated, identifying it
Version Which Jarvis build you are running
Counts How many organizations, users, agents and assets
Public URL Your instance's address — optional, see below
Timestamps When the process started, and when it reported

Counts, not contents. No names, no email addresses, no conversation text, no asset inventory, no credentials, nothing about what you administer. The only field that identifies your network rather than measuring something is the public URL, and it can be switched off.

The reply can carry a renewed key, which your instance adopts on its own — so a renewal reaches you without anybody re-pasting anything.

Your platform does not stop working because of a licence. Expiry gives you a grace period, then refuses only the creation of new organizations, users and agents — everything already set up keeps running, and so does the assistant. There is no state in which Jarvis disables, deletes or locks you out of something you are already using. If the check-in cannot reach the server, nothing changes at all: the key you hold is what governs, and it is checked without a network.

Licence

The images are provided as-is with no warranty, no support and no commitment to future availability. The source is not public and no rights to it are granted. Ask the maintainer before deploying this commercially or for third parties.

S
Description
Run Jarvis on your own Docker host, from published container images. No source, no build.
Readme
1,013 KiB
Languages
SVG 100%