Download Obol 0.1.0 for Linux AMD64 or ARM64. The release includes pinned images, Compose, Helm, an offline archive, license notices and signed checksums.
Free Self-hosted is a permanent, customer-operated edition of the same control plane Obol Cloud runs. You run it on your own infrastructure, bring your own model and tool credentials, and govern calls with scoped keys, policies, approvals, and receipts that name their evidence class. Obol charges no software or per-call fees on your installation. There is no 30-day expiry, no card, and no Obol call quota; your infrastructure and vendor bills are yours, paid directly to those vendors. The downloads use the proprietary Obol Self-Host Distribution License 1.0. It permits free commercial self-hosting, private mirrors and backups, and using Obol within your customer applications. Redistribution and offering Obol itself as a hosted or managed service require a separate agreement. The complete terms ship in the release bundle’s NOTICE; third-party components retain their own licenses. The application repository remains private.

What you operate, and what Obol offers

Managed hosting (Obol Cloud) and contracted Enterprise support are available separately if you want someone else to carry the operating responsibility. They are offered, never required: nothing in a Free Self-hosted installation stops an allowed call to ask you to upgrade.
Running the free distribution establishes no support relationship with Obol. A Free Self-hosted installation reports itself as customer-operated with no contracted support, and it never displays an Enterprise operations claim.

Honest limits

  • Single host, Docker Compose. The supported topology is one Linux host running the release’s Compose file. Kubernetes, high availability, VPC delivery and support are contracted separately. The offline bundle carries the same release images for hosts without registry access.
  • No SLA. There is no uptime, response-time, or recovery commitment.
  • One installation owner. The first release enrolls a single local owner through a one-time setup token by default. Alternatively, select Clerk during installation and supply your own Clerk application keys.
  • You hold the keys. The installer generates the encryption identity, signing keys, and datastore passwords on your host. Back them up with the database — Postgres alone cannot recreate a lost key, and Obol cannot recover them for you.
  • Built-in assistance is absent unless you configure its credentials yourself; those surfaces render not evaluated rather than borrowing a credential.
  • Obol Workers and built-in tools are Obol Cloud services. They are not part of Free Self-hosted. Control refuses Workers and configures no built-in tools on a self-hosted installation, and it offers no prepaid balance there (ADR-0215).

Configure AI providers and the copilot

After signing in, open Settings → Configuration (/dashboard/settings/configuration). First run opens this page; it brings provider keys, copilot selection, setup progress and public server addresses together. The copilot also links directly to its AI configuration section.
  1. Add an OpenRouter, OpenAI, Anthropic, or compatible API key and an exact model ID from your provider account. Keys are write-only and vaulted; your provider bills you directly.
  2. Publish a workspace policy allowing that model. In Copilot model, select the published connection, review the monthly provider budget, request rate, output limit and timeout, and choose Enable copilot. Saving another model applies to new turns. Only a workspace owner can change copilot access.
  3. Optionally connect Jev decisions using an OpenRouter or direct TypeSafe key. Jev returns typed decisions rather than chat replies, so it has its own key form and is excluded from the copilot chat selector. These are ordinary governed decision-tool connections for your agents; connecting one does not configure the installation’s built-in evaluator.
OpenRouter’s Jev connector pins typesafe/jev-1.13; the direct TypeSafe connector pins jev-1.13.0. A compatible chat provider needs an HTTPS base URL ending in /v1. A pending connection is shown as publishing or needing a key; refresh its status before enabling the copilot. Under Server addresses, enter your dashboard, Control and gateway HTTPS origins and download obol-configuration.json. Copy it to the installation and run ./obol configure obol-configuration.json, then ./obol start and ./obol doctor. The download contains only public addresses; credentials and private keys remain in the installation. Downloading alone changes nothing on the running server.

Install with an AI agent

You can have a coding agent that runs on the target host, such as Claude Code, Codex, or Cursor, do the install. Copy one of the prompts below and give it to the agent. Both keep it to the documented steps: it may not read your keys, build from source, delete data, or expose ports, and it hands the owner setup token back to you.
  • One-line installer runs the same install.sh as the install command. The script checks the archive and bundle checksums; it does not verify the publisher signature. The tryobol.dev home page offers this prompt with a copy button.
  • Verified bundle downloads the release archive yourself and verifies the Cosign publisher signature before anything runs. Replace <RELEASE_ARCHIVE_URL> with the obol-selfhost-<version>.tar.gz link from the release page.

Install Obol Free Self-hosted on this machine with the one-line installer.

Open in Cursor
The setup token gives whoever holds it the installation owner role until the token is used or expires. When the agent hands off, run ./obol-selfhost setup-token (or obol setup-token in the install directory, for the one-line installer) yourself. That replaces the token printed during init, which the agent saw.

Install

Install Docker Engine and its Compose plugin first. On the host, run:
The script downloads a verified release into /opt/obol, retains its files in releases/<version>, generates secrets, pulls the pinned images, runs migrations, and starts the stack. Run as the directory’s owner, or choose a writable directory:
Select local owner login (default), Clerk (you supply keys), or no dashboard. For an unattended headless install, add --no-dashboard. Gateway, Control, worker, migrations, Postgres and Redis still run. Operator API authentication remains required; use the local owner setup API or configure Clerk before managing workspaces. No Rust, Node, Python or age installation is required on the host: age-keygen runs inside the release’s control image during initialization. The equivalent manual installation follows.
1

Download and verify the release

Download the obol-selfhost-<version>.tar.gz bundle and its .sha256 file from the public release page, along with RELEASE_SHA256SUMS and RELEASE_SHA256SUMS.sigstore.json. With Cosign installed, verify the publisher signature and downloaded files before running anything:
A checksum proves you have the bytes the release listed; it does not prove who published them. The signature authenticates the public Obol distribution workflow and all release bytes; release.json binds the image digests. It does not expose or independently prove the private build process. Every file you intend to use must appear in the signed index and report OK. The bundle contains the installer script, a Compose file that pins every application image by digest, release.json, notices, and a short guide. No source checkout, compiler, or registry login is needed.
2

Initialize

init checks Docker, architecture, ports, resources and permissions, then generates fresh installation secrets and a one-time owner setup token, writes configuration with restricted permissions, and refuses to overwrite existing key state. It records the Docker Compose project the installation runs under as COMPOSE_PROJECT_NAME in .env (obol-selfhost, or init --project NAME) and refuses a name another stack on the host already uses; every later command passes that name explicitly and ignores a COMPOSE_PROJECT_NAME exported in your shell. The only secret it prints is the one-time owner setup token, shown once when it is generated and never stored (only its hash is kept); no other secret value is printed.
3

Start

start pulls the release’s exact image digests, runs database migrations, and waits for the services to report healthy. It never builds from source. Product ports bind to 127.0.0.1 by default. It points you at /setup only while owner setup is open, and at the sign-in page once the owner exists.The containers have no restart policy: after a host reboot or a Docker restart, run ./obol-selfhost start again (or from a systemd unit or an @reboot cron entry).
4

Claim the installation owner

Open the dashboard at the loopback address init printed and go to /setup. Enter the setup token, your email, and a password. The token is single-use and expires; once the owner exists, setup is closed. If it expires first, run ./obol-selfhost setup-token on the host for a new one. Setup creates your owner account and a dev workspace with no policy permits of its own. Until you publish a policy, the built-in default refuses every tool call and permits only tool listing and model completions, which each key’s model allowlist still bounds.
5

Make a first governed read

Follow the first-run guide on the dashboard home: connect one of your own credentials (a model provider key, or a native connection such as GitHub), publish a narrow policy, and issue an ob_test_ key. Point your client at the local /v1 or /mcp endpoint the guide shows and make one read. Then open its receipt — on a native route it can be gateway_observed; the receipt always names the evidence class its route earned — and try one out-of-scope call to see it refused before it reaches the vendor.
To reach the installation from other machines, put it behind a reverse proxy with TLS and set the dashboard’s public HTTPS origin. Never publish Control’s internal listener (see the boundaries below).

Operating reference

The release Compose file runs the same binaries as managed hosting. Fly.io is the internal managed-hosting path. Source builds belong in the contributor Compose reference.

What runs

Every application image is published for linux/amd64 and linux/arm64 under ghcr.io/tryobol/gateway, ghcr.io/tryobol/control, and ghcr.io/tryobol/web, with the same version tag. Release Compose pins their digests and the digests of Postgres 16 Alpine and Redis 7 Alpine. It contains no build instructions. Control, gateway and web bind ${OBOL_BIND_ADDRESS:-127.0.0.1}; Postgres and Redis are loopback always. Widen OBOL_BIND_ADDRESS only behind a TLS reverse proxy. The default render runs the first seven services; landing and the Obol Workers services are Compose profiles that start only when named. The control image is one image with three roles selected by the container command: api, worker, and migrate. apps/control/docker-entrypoint.sh dispatches them.
There is no connectors service, in any target. Out-of-process Python workers are unimplemented — apps/connectors/ ships no package and no image — so the service, its connector-workers Compose profile, the Fly app and the Helm workload were all removed rather than left as a profile that could not start. Reviewed native connector packs run through the gateway’s own HTTP executor and need no worker. See Trusted workers.

Boundaries the topology enforces

  • The gateway has no DATABASE_URL. Postgres never sits on the streaming path; the gateway reads snapshots from Redis and falls back to control’s /internal/v1/*.
  • Only the gateway can unwrap a credential. No other workload receives a vendor secret or key-encryption key, and no target sets OBOL_CONNECTORS_URL. Were a worker deployment added, it would need a private-only address and an ingress policy admitting the gateway alone — and it would today receive the vendor credential over ADR-0014’s hand-off, which is why one has not been.
  • Control’s /internal/* is never routed by the public edge. Under Compose the whole control port is published on OBOL_BIND_ADDRESS (loopback by default), so put control behind a reverse proxy that allows only /api, /oauth, /.well-known, and /healthz when it is reachable from outside the host.
These are the same invariants the code enforces, restated at the infrastructure layer.

Required configuration

The installation’s .env contains its runtime configuration. Keep it mode 0600 and back it up together with secrets/age.key and the database. Dashboard authentication and public URLs are read at runtime. Changing the Clerk key or domain requires restarting the service, never rebuilding an image. The browser receives only the explicit public configuration; secret keys stay server-side. In Clerk mode, missing identity configuration fails closed. Compose refuses to start without POSTGRES_PASSWORD, REDIS_PASSWORD, OBOL_SERVICE_JWT_SECRET and OBOL_APPROVAL_REPLAY_KEY; none has a default. Outside OBOL_ENV=dev, control also refuses to start without the ADR-0131 service-signing key set (.env.example names it).
In clerk mode with no CLERK_ISSUER set, control answers 503 on every /api/v1 operator route rather than serving them unauthenticated. That is deliberate. A control plane that silently opens because a variable was unset is the worst available outcome.

Key-encryption key

OBOL_KEK_PROVIDER selects how vaulted credentials are wrapped.
The installer runs age-keygen and writes its public recipient into .env. The private identity is mounted as a Compose secret into the gateway only, at /run/secrets/age.key. Control gets OBOL_AGE_RECIPIENT and can therefore seal a credential but never open one. Connectors and web receive no key material at all. See vault and key management.

Optional configuration

Never set OBOL_FORWARDED_ALLOW_IPS to *. Scope it to the exact proxy addresses or ranges in front of control.
Leaving the Stripe variables blank is a supported self-host posture: usage is still recorded, and only delivery to Stripe waits. See metering.

Database migrations

migrate runs alembic upgrade head and exits. Both control and control-worker depend on it with the service_completed_successfully condition, so no application process starts against an un-migrated schema. Control also has a /healthz healthcheck the gateway waits on before it starts. The Kubernetes chart applies the same rule differently — a revision-named Job plus an init container that waits for the image’s full Alembic head set (ADR-0043). See Helm.

Upgrading

From the installation directory:
Or rerun the installer at the desired release:
Use the same --dir if you chose a custom directory. Without a version, ./obol upgrade downloads the latest published release. The upgrade verifies checksums and the declared upgrade floor, takes a Postgres dump and Redis/key backup, pulls the new digests, stops product services, runs migrations, then starts Control and the worker and waits for service health. Failed migrations stop the upgrade. Keep the backup; an older image cannot undo a schema change.
/startupz checks the gateway’s first-boot dependencies. /readyz is traffic readiness and returns 503 until the first workspace policy snapshot is published. After setup and policy publication, require it to return 200 before sending traffic. The installer never invents a permissive policy to make this probe pass.

What carries across upgrades and restores

These carry across upgrades and restores by design:
  • The Compose project. An installation made before COMPOSE_PROJECT_NAME was recorded is refused with a one-line fix rather than guessed at: add the project it runs under (docker compose ls; earlier releases used obol), for example printf 'COMPOSE_PROJECT_NAME=obol\n' >> .env, then run ./obol-selfhost doctor.
  • Restore on the same host. Restoring a backup next to the installation it came from (restore testing) needs its own project and ports, or it collides with the original: ./obol-selfhost restore <dir> --project NAME --ports-offset N (or explicit --web-port, --control-port, --gateway-port, --postgres-port, --redis-port). This rewrites only the restored copy’s ports and public URLs and is for restore testing only.
  • Login lockouts. Five failed sign-ins in 15 minutes lock the address for a browser the owner has not signed in from; a browser holding a live device credential is counted on its own bucket instead (ADR-0165). Lockouts live in Redis, which is persisted and backed up, so a restart, upgrade or restore does not clear them. They expire after the window, and ./obol-selfhost reset-owner-password --email <owner> clears them at once.
Never erase a database to repair a failed deployment. Inspect the migrate service logs first — a failed migration is a diagnosable state, and the data behind it is the audit record.

Moving a Clerk-mode installation to local sign-in

A release-bundle installation can switch from Clerk to local sign-in while keeping its data. Existing workspaces belong to Clerk users; local owner setup creates a new owner and a new dev workspace. The host operator can then grant that owner access to existing workspaces with adopt-workspace (ADR-0166). These commands run in the installed release directory. For an older, custom source deployment, first plan the transfer of its existing database, Compose project and encryption keys to the release bundle. Keep a verified backup; do not initialize an empty replacement over existing data.
1

Back up and upgrade the release

Keep the backup’s database, configuration and key-encryption material together.
2

Switch the mode and mint a setup token

In the installation’s protected .env, set OBOL_OPERATOR_AUTH_MODE=local, OBOL_CLOUD_HOSTED=false and OBOL_PUBLIC_SIGNUP_ENABLED=false, then run:
setup-token records the token’s hash and expiry, recreates Control, and shows the one-time token. It refuses to reopen an installation already claimed by a local owner. start applies the configuration to the remaining services.
3

Claim the owner

Open /setup, enter the token, and create the owner.
4

Adopt the existing workspaces

adopt-workspace runs only on a claimed, local-mode, self-hosted installation and only for the owner’s email. It adds the owner’s owner membership and never removes or changes anyone else’s, never contacts Clerk, is safe to run twice, and writes one selfhost.workspace_adopt audit row per workspace with the host as the actor. --include-group on an environment group’s anchor adopts its other environments too; without it they are listed and left alone.

Kubernetes and offline installs

Each release also contains obol-<version>.tgz, helm-values.yaml, and obol-bundle-<version>.tar. The chart uses the same application image digests. Download the chart and release values together, then supply your own datastore, key-encryption, identity, networking and service-signing configuration as described in Helm:
The offline tar contains both architectures, Compose, the vendored chart, application SPDX SBOMs, notices and checksums. Transfer the tar and its published checksum to the host, then:
The offline Compose override uses exact loaded image IDs with pull_policy: never. For an upgrade, load the new archive’s images first and run the installed ./obol upgrade /absolute/path/to/new-bundle/loaded-amd64 (or loaded-arm64). Restore into the matching offline release directory. Optional Bitnami database images and Obol Workers are not included; Kubernetes operators provide their own Postgres/Redis and import application images into their registry or nodes. External model and tool calls still need access to their vendor endpoints.

Enterprise features

Core gateway, policy, receipts, credential encryption and KMS remain available without a license. Contracted support, SCIM, audit streaming/export and BYOA use a signed offline license supplied with the agreement. No license or private signing key is stored in the repository or public images. Set OBOL_ENTERPRISE_LICENSE to the issued token in the protected .env, and OBOL_LICENSE_VERIFICATION_KEY to the issuer’s base64url Ed25519 public key supplied with it. Control checks the signature, expiry, feature set and workspace scope at startup and when protected capabilities are used. Existing verified Enterprise grants are still required. An absent or expired optional license leaves core operation running and denies premium capabilities. For a deployment that must refuse startup without BYOA entitlement, set OBOL_LICENSE_REQUIRED_FEATURES=byoa. Helm exposes this as enterprise.byoa=true, with enterprise.licenseSecret naming an existing Secret containing OBOL_ENTERPRISE_LICENSE and enterprise.verificationKey supplying the public issuer key. Do not commit license tokens to values files.

Data and state

Redis runs with --appendonly yes and --maxmemory-policy noeviction. The eviction policy is not incidental: snapshots and idempotency records must not be evicted under memory pressure. Back up pgdata. Redis is a cache and a stream buffer in front of Postgres, but usage events in flight are only in Redis until the worker drains them, so drain before a destructive Redis operation.

Next steps

Compose reference

Release commands and the separate contributor Compose workflow.

Helm

The Kubernetes path, for contracted deployments.

Invariants

The rules the deployment is shaped to enforce.

Telemetry

Metrics, traces, and where they go.