17 KiB
Install ThothII on a Linux server
This guide is for an installer with basic Linux administration and very basic Docker knowledge.
It deploys the same Compose distribution used on a local PC: the mandatory application is exactly
frontend plus core, and pinned Pi is inside core. The server does not need host Pi, Node.js,
Python, Go, a browser shell, or a Docker socket inside either container.
Examples use /srv/thothii as an example operator root, thoth.example.com as a replaceable
DNS name, and systemd-based command names. Adapt them to local policy. Complete the server session
storage overlay and migration procedure before exposing a production installation.
Deployment contract
- The generic Linux host and Docker Compose v2 are the deployment platform. No other application's Compose project, network, path, or runtime is required.
frontendis the only published service and defaults to127.0.0.1:8080;corehas no host port. A host Nginx or Caddy listener terminates TLS and sends all application traffic tofrontend, never directly tocore.- DWH, vector database, embedding service, and LLM are external configurable endpoints. This remains true when they happen to run on the same physical server.
- Application, Git, connector, and session credentials are protected host files mounted read-only
under
/run/secrets. Pi's protected auth JSON uses its dedicated read-only Pi mount. No secret value belongs in Git, images, browser storage, environment values, rendered Compose, or logs. - The Git-backed workspace registry is the source of truth. Installation-local bindings identify endpoints and secret-file paths; they do not replace the reviewed Git workspace descriptors.
thothctlis the operator CLI for start, stop, status, health, logs, Pi lifecycle, drain, and rollback. Raw Compose lifecycle commands bypass installation state and are unsupported.
Read server workspace-registry installation,
Pi management, and the session-server comments in
deploy/compose.session-server.yaml.example before the first public start.
Service account and directories
The container runtime identity is fixed at UID/GID 10001. Reserve the same host ID for a dedicated
non-login thothii account so bind-mounted ownership is obvious. Stop if either ID is already used
by another account; choose a reviewed host mapping instead of changing the image identity.
getent passwd 10001
getent group 10001
sudo useradd --system --uid 10001 --user-group --home-dir /srv/thothii \
--create-home --shell /usr/sbin/nologin thothii
The human operator who runs thothctl needs Docker access. On many installations membership in
the docker group is effectively host-root access; grant it only according to site policy. The
non-login thothii account owns application data and secrets but does not itself need Docker
access.
Create explicit directories. source contains the clone; operator contains untracked path-only
configuration; the three writable trees are bind-mounted into core; secrets contains regular
files only. Backups are separate from live data.
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/source
sudo install -d -o 10001 -g 10001 -m 0700 /srv/thothii/operator
sudo install -d -o 10001 -g 10001 -m 0700 /srv/thothii/secrets
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/data
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/pi-state
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/workspace-registry
sudo install -d -o root -g root -m 0700 /srv/thothii-backups
Do not make /srv/thothii a shared application directory. The source checkout may be read by the
operator, while secret contents and writable data remain limited to reviewed administrators and
UID 10001.
Firewall and network boundaries
Set THOTH_SERVER_BIND=127.0.0.1. Permit inbound TCP 80/443 only to the TLS proxy; port 80 should
redirect to HTTPS. Do not open 8080 externally, and do not add a core port. If a separate proxy
host is used, replace loopback with a private, firewalled address and allow only that proxy source.
Allow outbound DNS and HTTPS to the source/Git registries, plus only the configured ports for the
Git remote, DWH, vector database, embedding service, LLM, session PostgreSQL, and any approved
bastion. Docker's private thothii network carries only frontend↔core traffic. Do not attach
the mandatory stack to another application's network.
After start, confirm the host listens as intended:
sudo ss -lntp
Expected public listeners are the proxy on 80/443 and the frontend on loopback 8080. There must be no host listener for core port 8787.
Address co-resident external services
Endpoint values are resolved inside core. Therefore container 127.0.0.1 means the container
itself, not the Linux host. Prefer real DNS names with TLS, authentication, and firewall policy,
even for services on this physical server.
When DNS is unavailable for a host-published service, create an untracked override such as
/srv/thothii/operator/host-gateway.yaml and add it to the installation descriptor:
services:
core:
extra_hosts:
- "host.docker.internal:host-gateway"
Use host.docker.internal in the endpoint binding. The host-gateway mapping supplies routing;
it does not bundle or trust the target service. Keep the target port bound/firewalled for Docker
host access only. A stable internal DNS record is the preferred alternative.
Configure each boundary independently:
- DWH: read-only runtime identity, database/schema, verified TLS, and direct or REST endpoint.
- Vector database: endpoint plus exact database/schema, collection, distance metric, and writer policy declared by the reviewed workspace.
- Embedding service: endpoint and the collection/embedding pairing—model and dimensions must match the existing collection. Co-residence does not permit silently changing that pairing.
- LLM: authenticated endpoint selected through deployment and Pi configuration.
Never add those services to ThothII's mandatory Compose files. Follow the diagnostic protocol before enabling a workspace.
Prepare operator files and secrets
Clone with LF line endings, then verify before every build:
sudo -u thothii git -c core.autocrlf=false clone \
https://github.example.invalid/your-org/ThothII.git /srv/thothii/source/ThothII
cd /srv/thothii/source/ThothII
sudo -u thothii git config --local core.autocrlf false
bash scripts/verify-line-endings.sh
Copy the path-only server environment and installation descriptor:
sudo -u thothii cp deploy/env/server.env.example /srv/thothii/operator/server.env
sudo -u thothii cp docs/install/examples/thothii-installation.server.yaml \
/srv/thothii/operator/thothii-installation.yaml
sudo chmod 0600 /srv/thothii/operator/server.env \
/srv/thothii/operator/thothii-installation.yaml
Replace every placeholder with an absolute path. Use exactly one Git transport override. For
HTTPS, replace deploy/compose.git-ssh.yaml with deploy/compose.git-https.yaml. Keep the required
session-server overlay and generated connector-secret override. Optional host-gateway or pinned
image overrides go after them.
Create each credential as an independent regular file in /srv/thothii/secrets, owned by
UID/GID 10001 and mode 0600. The operator environment records only absolute *_FILE or
*_SOURCE paths. Compose mounts application and connector targets read-only under /run/secrets;
the frontend receives none. Do not print file contents while testing permissions.
sudo find /srv/thothii/secrets -type f -exec chown 10001:10001 {} +
sudo find /srv/thothii/secrets -type f -exec chmod 0600 {} +
sudo find /srv/thothii/secrets -type f ! -user thothii -print
sudo find /srv/thothii/secrets -type f ! -perm 0600 -print
Add THT_WORKSPACE_BINDINGS_ENV_FILE=/srv/thothii/operator/workspace-bindings.env and the matching
connector *_SOURCE paths to server.env. Generate
/srv/thothii/operator/connector-secrets.server.yaml as described in
server workspace-registry installation. Secret values must never
be pasted into server.env, the installation YAML, a URL, or a shell argument.
Build locally or select pinned images
Choose one image source. For a source build, the repository's reproducible launcher builds the
same core and frontend images used by the local profile. It requires only Git, Docker, and
Compose; copy the reviewed path-only server environment to the launcher's untracked input first:
cd /srv/thothii/source/ThothII
sudo -u thothii cp /srv/thothii/operator/server.env deploy/env/local.env
bash scripts/build-local.sh
The printed local-profile start command is not the server start command; use thothctl below.
Alternatively, create a reviewed untracked override with release images pinned by immutable digest. Mutable tags are not a production pin:
services:
core:
build: !reset null
image: registry.example.com/thothii/core@sha256:<64-lowercase-hex-digits>
frontend:
build: !reset null
image: registry.example.com/thothii/frontend@sha256:<64-lowercase-hex-digits>
Add that absolute file last in overrides. Both images must come from one compatible release; the
core image must retain the declared Pi version labels checked by thothctl pi doctor. Pull access
belongs in the host Docker credential store, not in Compose or the installation descriptor.
Install thothctl
Build the operator binaries with Docker. No Go installation or Go knowledge is required:
cd /srv/thothii/source/ThothII
bash scripts/build-thothctl.sh
sudo install -o 10001 -g 10001 -m 0755 dist/thothctl/thothctl-linux-amd64 \
/srv/thothii/operator/thothctl
Use thothctl-linux-arm64 on an ARM64 server. Set these variables in the maintenance shell; do
not source server.env as shell code:
THTCTL=/srv/thothii/operator/thothctl
INSTALLATION=/srv/thothii/operator/thothii-installation.yaml
"$THTCTL" --installation "$INSTALLATION" --help
"$THTCTL" --installation "$INSTALLATION" update --check-only
Every operator command includes the descriptor explicitly. This preserves the installation's
profile, overrides, project identity, and durable current-image selector. The general form is
thothctl --installation /absolute/path/thothii-installation.yaml <command>.
Start and verify readiness
Keep the TLS proxy stopped or firewalled during bootstrap:
"$THTCTL" --installation "$INSTALLATION" start
"$THTCTL" --installation "$INSTALLATION" status
"$THTCTL" --installation "$INSTALLATION" doctor
curl --fail http://127.0.0.1:8080/health
"$THTCTL" --installation "$INSTALLATION" pi doctor
"$THTCTL" --installation "$INSTALLATION" pi test
/health proves process liveness. Readiness additionally requires both healthy services, a valid
Pi provider/model smoke, a successful Git registry pull with an active validated snapshot, valid
workspace diagnostics, and ready session PostgreSQL. Use the authenticated Workspace Management
page to pull and diagnose the reviewed workspace. A liveness response alone is not release
approval.
After configuring the proxy, open https://thoth.example.com in a browser. Verify an unauthenticated
request is denied or redirected by the real identity provider, an authorized user can load the
same-origin UI and /api, an unauthorized user is denied, and an administrator alone can open Pi
Management. Keep port 8080 inaccessible from other hosts.
Configure TLS and upstream authentication
Choose Nginx or Caddy. Both examples terminate
TLS and proxy only to loopback frontend. They preserve SSE and clear client-supplied identity
headers before authentication.
The authentication gateway must validate a real login/session and return normalized issuer,
subject, display-name, and admin claims only after success. Merely forwarding those headers does
not authenticate anyone. Do not enable AUTH_MODE=upstream on a listener reachable around the
trusted proxy, and never expose core.
Operate Pi, drain, and roll back
Configure only closed provider/model/reasoning choices. Credentials remain protected files:
"$THTCTL" --installation "$INSTALLATION" pi status
"$THTCTL" --installation "$INSTALLATION" pi configure
"$THTCTL" --installation "$INSTALLATION" pi doctor
"$THTCTL" --installation "$INSTALLATION" pi logs
Before an update, announce maintenance and ask users to finish active work. --drain closes new
admission and waits until no active sessions remain; it does not discard sessions. Build-source
and registry-source examples are:
"$THTCTL" --installation "$INSTALLATION" pi update \
--version 0.81.0 --source build --yes --drain
"$THTCTL" --installation "$INSTALLATION" pi update \
--version 0.81.0 --source pull \
--image registry.example.com/thothii/core@sha256:<64-lowercase-hex-digits> \
--yes --drain
The transaction recreates only core, preserves volumes, verifies health/configuration/Pi, and
automatically attempts rollback after a post-mutation failure. For interrupted or ambiguous state:
"$THTCTL" --installation "$INSTALLATION" pi maintenance status
"$THTCTL" --installation "$INSTALLATION" pi rollback --yes
"$THTCTL" --installation "$INSTALLATION" pi maintenance recover --yes
Leave maintenance active if rollback cannot be verified. Preserve .thothctl/<installation-id>/
recovery state, repair the reported host/configuration issue, and rerun rollback or maintenance
recovery. Never delete or edit current-image.yaml or update-state.json to force progress.
Back up and restore
Back up before source, workspace, session-schema, or Pi changes. Drain work, stop the installation,
record git rev-parse HEAD, image digests, and thothctl status, then archive the three bind trees
with numeric ownership. Do not include live secrets in this ordinary archive.
"$THTCTL" --installation "$INSTALLATION" stop
BACKUP=/srv/thothii-backups/2026-08-05
sudo install -d -o root -g root -m 0700 "$BACKUP"
sudo tar --numeric-owner --xattrs --acls -C /srv/thothii -czf "$BACKUP/runtime-data.tgz" \
data pi-state workspace-registry
sudo sha256sum "$BACKUP/runtime-data.tgz" >"$BACKUP/SHA256SUMS"
Back up the installation descriptor, path-only environment, generated overrides, source revision, and secret files to separate encrypted access-controlled storage. Database-backed production sessions require their own PostgreSQL-native consistent backup; the local bind tree is not a substitute. Test both restore paths periodically.
Restore only while stopped. Verify the checksum, extract first into a new empty root, inspect ownership and expected registry layout, then retain the old trees by renaming them before placing the restored set. This keeps the previous state recoverable:
RESTORE=/srv/thothii-restore-2026-08-05
sudo install -d -o root -g root -m 0700 "$RESTORE"
sudo sha256sum --check /srv/thothii-backups/2026-08-05/SHA256SUMS
sudo tar --numeric-owner --xattrs --acls -C "$RESTORE" \
-xzf /srv/thothii-backups/2026-08-05/runtime-data.tgz
sudo test -d "$RESTORE/workspace-registry/repo"
sudo test -d "$RESTORE/workspace-registry/snapshots"
During the reviewed restore window, move each old tree to a timestamped sibling, move the matching
restored tree into /srv/thothii, restore the PostgreSQL session backup from the same recovery
point, and keep the proxy closed. Run update --check-only, start, doctor, pi test, registry
status, workspace diagnostics, and a known historical session before reopening traffic. Never
merge an archive into a non-empty tree.
Diagnostics
Begin with bounded, sanitized installation-aware commands:
"$THTCTL" --installation "$INSTALLATION" status
"$THTCTL" --installation "$INSTALLATION" doctor
"$THTCTL" --installation "$INSTALLATION" logs
"$THTCTL" --installation "$INSTALLATION" pi status
"$THTCTL" --installation "$INSTALLATION" pi doctor
"$THTCTL" --installation "$INSTALLATION" pi test
"$THTCTL" --installation "$INSTALLATION" pi logs
"$THTCTL" --installation "$INSTALLATION" pi maintenance status
Use the authenticated Workspace Management status and diagnostic actions for Git revision, degraded snapshot, bindings, DWH, vector, and embedding checks. Review proxy logs separately, but configure both proxy and log shipping to exclude cookies, authorization data, identity payloads, query strings, and secret values. Do not render Compose or print an environment as a diagnostic.
Typical boundaries are: doctor for Docker/Compose/LF/volume/service health; pi doctor for image
and provider/model integrity; registry status for Git/snapshot health; workspace diagnostics for
external service identity; and the proxy/identity provider for login failures.
Data-preserving uninstall
Drain and stop through thothctl, take and verify one final backup, disable the TLS proxy route,
and remove only this installation's stopped frontend and core containers and optional images
by their exact Compose project labels. Keep /srv/thothii/data, pi-state,
workspace-registry, operator, protected secrets, database backups, and the installation
descriptor if reinstallation is possible. Do not prune global Docker data.
Do not run docker compose down --volumes; it deletes persistent application data. Reusing the
same protected descriptor path preserves the thothctl installation identity and allows a later
compatible source checkout to reconnect the retained state.