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 anhttpsendpoint. 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#
-
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 anhttp://address. -
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.
-
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 .envdocker loadadds the imagetoryo-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. -
Edit
.env.TORYO_VERSIONis already set to the bundle's version. SetTORYO_GATEWAY_KEYto the key from step 2, and both passwords,TORYO_KNOWLEDGE_PG_PASSWORD(the database owner) andTORYO_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:
- app-role creates the restricted
toryo_approle if it is missing and sets its password from.env. It can never log in as a superuser or bypass row-level security. - 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#
upfails with a message naming a variable:.envis missing a required value, most often one of the two passwords.service "provision" didn't complete successfully: readdocker 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 rejectsTORYO_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.envand rundocker 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 showlicense 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_DOMAINis unset, or the name does not resolve to this host, or ports 80 and 443 are not reachable from the internet;docker compose logs caddysays which.