Self-host your team's knowledge

On the Enterprise plan, your team's shared knowledge can live on your own servers instead of on toryo's hosted service. You run the brain gateway and its Postgres from a Docker Compose bundle. It is the same stack toryo runs for its hosted service: compiled toryo binaries in a container image, no source, no model server. Your members' machines reach it over HTTPS with their seat.

To compare this with the other places your team's knowledge can live, see Where your team's knowledge lives.

Requirements#

  • An x86-64 (amd64) Linux host with Docker Engine and Docker Compose v2.20 or later. The gateway image ships for linux/amd64 only, and the bundle relies on --wait, one-shot setup services and profiles.
  • An https:// address for the gateway. The portal accepts only an https endpoint. Either:
    • use the bundle's tls profile: a public DNS name pointing at the host, with ports 80 and 443 open, and Caddy obtains the certificate; or
    • put the gateway behind your own HTTPS load balancer or reverse proxy.
  • A team license on the Enterprise plan, and owner or admin access to it in the portal. The Team plan does not include self-hosting. Steps 1 and 2 below need an owner; an admin can download the bundle and the image.

Set it up#

  1. In the portal, open the Knowledge page. On your team's Hosting card, choose Remote, then Self-hosted, and enter the https:// address the gateway will answer on. The portal refuses an http:// address.

  2. On the same card, choose Create gateway key. The key is shown once; copy it now. The gateway uses it to prove which team it serves.

  3. In the portal, open Self-hosting and choose Download for the bundle and for the image. The page offers both for the latest toryo release and shows the version and each file's sha256. Check both downloads against those, load the image, then unpack the bundle:

    shasum -a 256 toryo-brain-gateway-bundle-<version>.tar.gz \
      toryo-brain-gateway-<version>-linux-amd64.tar.gz   # compare with the portal's sha256s
    docker load -i toryo-brain-gateway-<version>-linux-amd64.tar.gz
    tar -xzf toryo-brain-gateway-bundle-<version>.tar.gz
    cd toryo-brain-gateway-bundle-<version>
    cp env.template .env
    

    docker load adds the image toryo-brain-gateway:<version>, the one the bundle's compose file names. Compose never pulls it, so a missing image fails with "no such image" rather than reaching for a registry.

  4. Edit .env. TORYO_VERSION is already set to the bundle's version. Set TORYO_GATEWAY_KEY to the key from step 2, and both passwords, TORYO_KNOWLEDGE_PG_PASSWORD (the database owner) and TORYO_KNOWLEDGE_APP_PG_PASSWORD (the restricted role the gateway serves requests as). The file's comments describe every other setting.

Then start it:

docker compose up -d --wait                  # behind your own HTTPS proxy
docker compose --profile tls up -d --wait    # with Caddy terminating TLS

For the tls profile, also set TORYO_GATEWAY_DOMAIN in .env to the DNS name. Without the profile, the gateway listens on 127.0.0.1:8080 over plain HTTP; TORYO_GATEWAY_BIND and TORYO_GATEWAY_PORT change that for a proxy on another host. Check it with curl -fsS http://127.0.0.1:8080/health, which answers {"ok":true,"license":{"state":"active",…}}.

What a start does#

Every up runs two one-shot steps before the gateway starts, and each is safe to repeat:

  1. app-role creates the restricted toryo_app role if it is missing and sets its password from .env. It can never log in as a superuser or bypass row-level security.
  2. provision creates the five knowledge databases, runs their migrations and applies the row-level security policies that keep each seat's data separate.

The gateway starts only after both succeed. It then activates its gateway key with toryo's license service, and serves only seats of your team: a member's seat that is revoked in the portal stops working at the gateway's next daily check-in.

If the license service cannot be reached, a running gateway keeps serving, and a restarted one starts from the last check-in it saved in the toryo-brain_gateway-state volume, for up to ten days after its last successful check-in. Past that it refuses every request until it can check in again. The license object in /health shows the state (active, grace or expired) and when the grace ends.

Members#

The address you entered in the portal is carried inside each member's signed team license, so members never enter it and a member cannot point the team elsewhere.

The owner gives each member a seat; see Joining a team. Members then connect their checkouts as in Team projects. From then on, what their agents save about a team project goes to your servers. Knowledge the team kept on members' machines before you switched moves there by itself; see Moving to hosting.

When the owner revokes a seat in the portal, the gateway refuses it from its next daily check-in: the member's knowledge commands in team projects exit 1 with the gateway's reason. toryo license refresh picks up the revocation on the member's machine, and toryo license remove <id> drops the team license, with the id toryo license list prints at the start of the team's line.

Embeddings#

No model runs on the server. Each member's machine computes its own embeddings, and the gateway only checks that every stored vector came from the same model. TORYO_EMBED_MODEL must therefore match the embedding model every member uses. It defaults to nomic-embed-text, the toryo default; leave it unset unless your team changed the model.

Upgrade#

Download the new version's bundle and image from the portal's Self-hosting page, as in step 3. The page always offers the latest release, so upgrade when a new one appears there. Check both against the sha256s the page shows, then docker load -i toryo-brain-gateway-<version>-linux-amd64.tar.gz. Unpack the bundle beside the old one and copy your .env across, changing only TORYO_VERSION to the new version. Then, in the new directory:

docker compose up -d --wait    # add --profile tls if you use it

The data lives in a Docker volume, not the bundle directory, so it carries over. The provision step migrates the databases to the new version before the new gateway starts.

Back up and restore#

All knowledge lives in the toryo-brain_pgdata volume. Back it up with a logical dump of the whole Postgres cluster, roles included:

docker compose exec -T postgres \
  sh -c 'pg_dumpall -U "$POSTGRES_USER"' > toryo-brain-$(date +%F).sql

The single quotes matter: $POSTGRES_USER is the owner name inside the container, set from your .env.

To restore onto a fresh volume:

docker compose down                       # stops everything, keeps the volume
docker volume rm toryo-brain_pgdata       # only when replacing the data
docker compose up -d --wait postgres
docker compose exec -T postgres \
  sh -c 'psql -U "$POSTGRES_USER" -d postgres' < toryo-brain-<date>.sql
docker compose up -d --wait

The restore reports that the owner role and its database already exist; both errors are expected. The final up resets the toryo_app password from .env and reapplies provisioning.

Passwords#

Postgres takes the owner password from .env only when the volume is first created. To change it later, change it inside Postgres first, then in .env, then run docker compose up -d --wait:

docker compose exec postgres \
  sh -c 'psql -U "$POSTGRES_USER" -d postgres -c "\password $POSTGRES_USER"'

The toryo_app password is different: change it in .env and run docker compose up -d --wait, which applies it.

Troubleshooting#

  • up fails with a message naming a variable: .env is missing a required value, most often one of the two passwords.
  • service "provision" didn't complete successfully: read docker compose logs provision. It names the database that failed.
  • The gateway exits at start: read docker compose logs gateway. It refuses to start without the database and restricted-role settings, which the bundle normally provides, and when the license service rejects TORYO_GATEWAY_KEY: the key was mistyped, was rotated in the portal, or the team is no longer self-hosted. Create a new key on the Hosting card, put it in .env and run docker compose up -d --wait. A team on the Team plan that was already self-hosted cannot create or rotate a key there; contact toryo.
  • Every request answers 503 gateway license expired: the gateway has not checked in with the license service for longer than its grace. The logs show license refresh failed: with the reason; it recovers on its own at the next hourly retry once the service is reachable and the key is current.
  • Caddy restarts in a loop: TORYO_GATEWAY_DOMAIN is unset, or the name does not resolve to this host, or ports 80 and 443 are not reachable from the internet; docker compose logs caddy says which.