Self-Hosting mit Docker
Dein eigenes Personal Brain in einem Container — auf deinem Laptop, einem kleinen NAS, oder einem Hetzner VPS. Dasselbe Image, dieselben Befehle.
Status: Stabil. Image wird bei jedem Push zu
mainautomatisch gebaut und unterghcr.io/gsalami/gts-personal:latestveröffentlicht (amd64 + arm64).
Warum Self-Hosting
- Datensouveränität — deine Brains liegen als Markdown-Files auf deinem Rechner, nicht auf einem fremden Server.
- Offline-fähig — der lokale Brain läuft weiter, wenn das Internet down ist. Sync zum Business passiert sobald du wieder online bist.
- Outbound-only — die Sync-Richtung geht immer vom Personal aus zum Business. Du musst keinen Port nach aussen öffnen, keine DynDNS, keine Portweiterleitung. Firewall-/NAT-freundlich.
- Kosten: 0 €. Gratis-Image, läuft auf deiner vorhandenen Hardware.
Variante A · lokal auf Mac / Windows / Linux
Voraussetzungen
- Docker Desktop (docker.com/products/docker-desktop) auf Mac/Win, oder Docker Engine auf Linux.
- 2 GB freier RAM + 500 MB Disk.
- Ein eindeutiger Slug (z.B.
alex) — falls du dich später mit einem Business Brain verbindest, muss der Slug matchen mit dem was dir dort im Invite steht.
Setup in 5 Schritten
mkdir my-brain && cd my-brain
curl -O https://raw.githubusercontent.com/gsalami/ground-truth-system/main/docker/docker-compose.yml
curl -O https://raw.githubusercontent.com/gsalami/ground-truth-system/main/docker/.env.example
mv .env.example .env
# → .env in deinem Editor öffnen, GTS_INSTANCE_SLUG + GTS_OWNER_EMAIL setzen
docker compose up -d
Beim ersten Start zieht Docker das Image (~250 MB, einmalig), scaffold't
/data mit deinem Slug + Owner, erstellt ein frisches Git-Repo darin,
startet den Server. Logs live mitlesen:
docker compose logs -f
Dann im Browser: http://localhost:3100/bootstrap → Admin-Passwort vergeben (min. 12 Zeichen) → du bist drin.
Konfiguration (.env)
| Variable | Pflicht | Beispiel | Erklärung |
|---|---|---|---|
GTS_INSTANCE_SLUG |
ja | alex |
Dein eindeutiger Slug. Kleinbuchstaben, Bindestriche. Muss stabil bleiben — steht in jeder Brain-ID drin. |
GTS_OWNER_EMAIL |
ja | alex@kuble.com |
Wird Admin-Account + Git-Author deiner Commits. |
GTS_PUBLIC_URL |
nein | http://localhost:3100 |
URL unter der du's aufrufst. Nur relevant wenn du's hinter Reverse-Proxy steckst. |
Nur beim ersten docker compose up werden SLUG + OWNER gelesen — danach
liegt die Config im gts-data Volume und ändert sich nicht mehr, selbst
wenn du die .env anpasst.
Mit einem Business Brain verbinden
- Auf dem Business Brain (z.B.
ground.kuble.com) einen Invite generieren:/admin/invites→ neuer Invite für deine Email - Invite-Code kopieren
- Bei dir lokal:
http://localhost:3100/admin/connections/new - Einfügen: Invite-Code + Business-URL (z.B.
https://ground.kuble.com) → Verbinden - Unter
/sync→ Alle pullen drückt den initialen Pull - Brains mit
sync.direction: pushin deinem Personal werden beiAlle pushenans Business gespiegelt
Alltag
docker compose logs -f # live mitlesen
docker compose restart # neustarten
docker compose down # stoppen, Daten bleiben erhalten
docker compose pull && \
docker compose up -d # Update auf neueste Version
# Backup (tar.gz im aktuellen Ordner)
docker run --rm -v gts-data:/src -v "$(pwd)":/dst alpine \
tar czf /dst/brain-backup-$(date +%F).tar.gz -C /src .
Metadata-Backups (meta.sqlite)
Die SQLite-Metadatenbank (meta.sqlite) enthält Sessions, Connections,
API-Keys, OAuth-State, Health-Checks und Chat-History. Brain-Inhalte selbst
liegen in git — die sind durch den Git-Repo schon versioniert.
Auf VPS-Setups (Variante B unten, systemd) installierst du einen stündlichen Backup-Timer per Repo-Script:
cd /opt/gts-<slug>
bash scripts/install-backup.sh
Was das macht:
- Kopiert
backup-db.shnach/opt/gts-scripts/ - Installiert
gts-backup@<slug>.timer(stündlich, mit 0-5 min Jitter) +.serviceTemplate - Legt den ersten Snapshot sofort an unter
/var/backups/gts-<slug>/ scripts/update.shmacht zusätzlich einenpre-update-Snapshot vor jedem Service-Restart
Rotation: letzte 7 Tage alles, dann wöchentlich (Mo 02:00Z) für 4 Wochen,
monatlich (1. 02:00Z) für 3 Monate. Tagged Backups (pre-update,
pre-deploy-*) werden nie automatisch gelöscht.
On-demand:
/opt/gts-scripts/backup-db.sh <slug> [optional-tag]
Jeder Snapshot läuft durch PRAGMA integrity_check vor UND nach der Extraktion
— korrupte Backups landen gar nicht erst auf Disk.
Restore (Disaster-Recovery):
systemctl stop gts-<slug>
cd /tmp && tar xzf /var/backups/gts-<slug>/<timestamp>.tar.gz
cp /tmp/meta.sqlite /var/lib/gts-<slug>/.gts/meta.sqlite # Pfad variiert
systemctl start gts-<slug>
Variante B · Hetzner VPS mit HTTPS
Gleiches Image, aber mit Traefik als Reverse-Proxy davor, der automatisch Let's-Encrypt-Zertifikate zieht.
Voraussetzungen
- Hetzner (oder ähnlicher) VPS — CX22 reicht (2 vCPU, 4 GB RAM, ~5 €/Monat)
- Ubuntu 22.04+ mit Docker + Compose-Plugin
- Traefik läuft bereits als Reverse-Proxy im Docker-Netzwerk
proxy(Standard-Setup auf Kuble-Servern) - DNS A-Record:
brain.deinedomain.tld→ VPS-IP
Setup
# auf dem VPS, als root
mkdir -p /opt/gts-brain && cd /opt/gts-brain
curl -O https://raw.githubusercontent.com/gsalami/ground-truth-system/main/docker/docker-compose.yml
curl -O https://raw.githubusercontent.com/gsalami/ground-truth-system/main/docker/docker-compose.vps.yml
curl -O https://raw.githubusercontent.com/gsalami/ground-truth-system/main/docker/.env.example
mv .env.example .env
# → .env bearbeiten: GTS_INSTANCE_SLUG, GTS_OWNER_EMAIL, GTS_HOST=brain.deinedomain.tld
docker compose -f docker-compose.yml -f docker-compose.vps.yml up -d
Traefik erkennt den Container über Labels, holt sich ein Let's-Encrypt-Cert
(~30 Sek), danach ist https://brain.deinedomain.tld/bootstrap erreichbar.
Update-Flow ist identisch — docker compose pull reicht.
Aus dem Source bauen (für Contributors)
Wenn du am Code arbeitest und dein Image lokal bauen willst statt das fertige zu ziehen:
git clone https://github.com/gsalami/ground-truth-system.git
cd ground-truth-system/docker
cp .env.example .env && $EDITOR .env
docker compose up -d --build
Der Build dauert ~3 Min (Next.js kompilieren + better-sqlite3 nativ bauen).
--build überstimmt den image:-Tag in der Compose-Datei.
Troubleshooting
GTS_INSTANCE_SLUG and GTS_OWNER_EMAIL are not set
→ .env nicht angelegt oder die Werte fehlen. docker compose down,
fixen, neu starten.
Port 3100 belegt
→ In docker-compose.yml "127.0.0.1:3100:3100" → "127.0.0.1:3200:3100"
ändern, dann läuft's auf http://localhost:3200.
Pull/Push failed — unauthorized
→ Invite ist abgelaufen (24h TTL) oder API-Key wurde rotiert. Neuen
Invite generieren am Business, unter /admin/connections/<id> rotieren.
"Duplicate column" / Migration-Fehler
→ Sollte nie passieren. Wenn doch: Logs posten, docker compose down →
docker volume rm gts-data (⚠️ löscht alles lokal) → neu starten.
Ich will in die SQLite reinschauen
docker exec -it gts-brain sh
# im Container:
apk add --no-cache sqlite
sqlite3 /data/.gts/meta.sqlite
Datensicherheit
GTS_AUTH_SECRET(signiert Sessions) wird beim ersten Start generiert und in/data/.gts/auth.secret(chmod 600) persistiert. Geht beimdocker volume rm gts-dataverloren — Nutzer müssen sich dann neu einloggen.- Integrationen (Recall.ai etc.) speichern API-Keys in
meta.sqlite(unverschlüsselt). Disk-Encryption (FileVault / LUKS) sinnvoll. - Brain-Inhalte sind Markdown — keine Secrets rein. API-Keys gehören in
.env-Files oder Passwortmanager.
Deinstallieren
docker compose down -v # stoppt + löscht Daten-Volume
docker image rm ghcr.io/gsalami/gts-personal:latest