Wer den Chastity Tracker nicht über das Self-Service-Portal beziehen möchte, kann die Anwendung vollständig auf einem eigenen Server betreiben. Das Docker-Image ist öffentlich verfügbar — ihr behaltet damit die vollständige Kontrolle über eure Daten, euren Server und eure Updates.
Diese Anleitung richtet sich an Personen mit Grundkenntnissen in Linux und Docker. Die Software wird ohne Gewähr bereitgestellt.
Aktualisiert am 30. Juli 2026: Abschnitt zu Image-Tags und Release-Kanälen ergänzt.
Voraussetzungen
Bevor ihr beginnt, stellt sicher, dass folgendes vorhanden ist:
- Ein Linux-Server (VPS oder Heimserver) mit Root- oder Sudo-Zugang
- Docker und Docker Compose installiert
- Eine Domain oder Subdomain, die auf die IP-Adresse eures Servers zeigt
- Port 3000 am Server erreichbar (oder ein Reverse Proxy, der davor sitzt)
Das GitHub-Repository findet ihr unter github.com/trublue-2/chastitytracker. Dort sind auch Release-Notes und bekannte Probleme dokumentiert.
Docker Compose Setup
Legt ein Verzeichnis für die Installation an und erstellt darin eine docker-compose.yml:
mkdir chastitytracker && cd chastitytracker
services:
chastitytracker:
image: ghcr.io/trublue-2/chastitytracker:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
NEXTAUTH_SECRET: "your-secret-here"
NEXTAUTH_URL: "https://your-domain.com"
volumes:
- ./data:/app/data
Das Volume ./data:/app/data sorgt dafür, dass die SQLite-Datenbank und alle hochgeladenen Fotos auch nach einem Container-Neustart oder Update erhalten bleiben. Ohne dieses Volume gehen alle Daten beim nächsten docker compose pull verloren. Welches Image-Tag ihr hier eintragt, ist eine eigene Entscheidung — siehe Abschnitt Image-Tags: Release-Kanäle und Version pinnen weiter unten.
Konfiguration (.env)
Die Umgebungsvariablen können direkt in der docker-compose.yml gesetzt werden (wie oben gezeigt) oder in eine separate .env-Datei im selben Verzeichnis ausgelagert werden. Wird eine .env-Datei verwendet, muss diese explizit in der docker-compose.yml referenziert werden — andernfalls werden die Werte nicht an den Container weitergegeben:
services:
chastitytracker:
env_file: .env
...
Die .env-Datei selbst enthält dann die Variablen ohne Einrückung:
NEXTAUTH_SECRET=your-secret-here
NEXTAUTH_URL=https://your-domain.com
NEXTAUTH_SECRET ist zwingend erforderlich. Generiert einen zufälligen String mit mindestens 32 Zeichen, zum Beispiel:
openssl rand -base64 32
NEXTAUTH_URL muss exakt mit der URL übereinstimmen, unter der die App erreichbar ist — inklusive Protokoll (https://) und ohne abschliessenden Schrägstrich.
Initialer Admin-Account
Ohne einen Admin-Benutzer lässt sich die App nach dem Start nicht sinnvoll nutzen. Legt beim ersten Deployment folgende Variablen fest — der Admin-Account wird beim Start automatisch erstellt, falls noch kein Admin in der Datenbank existiert:
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-strong-password
ADMIN_EMAIL=admin@your-domain.com
Diese Variablen werden nur beim allerersten Start ausgewertet. Danach können sie entfernt oder leer gelassen werden.
SMTP (optional, empfohlen)
Benachrichtigungen per E-Mail funktionieren nur, wenn SMTP konfiguriert ist. Fügt folgende Variablen hinzu:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-user@example.com
SMTP_PASS=your-password
SMTP_FROM=noreply@your-domain.com
Ohne SMTP-Konfiguration läuft die App, aber E-Mail-basierte Funktionen (Benachrichtigungen, Passwort-Reset) stehen nicht zur Verfügung.
KI-Inspektionsverifizierung (optional)
Der Tracker kann handgeschriebene Codes in Inspektionsfotos automatisch erkennen (Claude Vision). Dafür ist ein Anthropic API-Key nötig:
ANTHROPIC_API_KEY=sk-ant-...
Ohne diesen Key läuft die App vollständig — die automatische Bildanalyse bei Kontrollen ist schlicht deaktiviert.
Push-Benachrichtigungen (optional)
Für Web-Push-Benachrichtigungen (PWA) werden VAPID-Schlüssel benötigt. Generiert sie einmalig mit:
node -e "const c=require('crypto').createECDH('prime256v1');c.generateKeys();console.log(c.getPublicKey('base64url'));console.log(c.getPrivateKey('base64url'))"
VAPID_PUBLIC_KEY=generated-public-key
VAPID_PRIVATE_KEY=generated-private-key
VAPID_SUBJECT=mailto:admin@your-domain.com
Passkey / WebAuthn (optional)
Damit Face ID, Touch ID oder andere Passkeys in der Produktion funktionieren, müsst ihr die Domain konfigurieren:
WEBAUTHN_RP_ID=your-domain.com
WEBAUTHN_RP_ORIGIN=https://your-domain.com
Diese Variablen sind für den lokalen Betrieb nicht nötig — ohne sie greift der Fallback auf localhost.
Starten
Startet den Container mit:
docker compose up -d
Beim ersten Start initialisiert die Anwendung die Datenbank automatisch und legt den Admin-Account an (sofern ADMIN_USERNAME gesetzt ist). Danach ist die App unter eurer Domain erreichbar.
Prüft den Status mit:
docker compose logs -f
Sobald die Zeile Ready on http://0.0.0.0:3000 erscheint, ist die App erreichbar.
Beim ersten Start gibt der Container eine Diagnose-Box aus, die zeigt welche Konfigurationswerte geladen wurden — hilfreich zur Fehlersuche:
┌─────────────────────────────────────────────────────┐
│ STARTUP DIAGNOSE │
│ ADMIN_USERNAME : admin │
│ ADMIN_EMAIL : admin@your-domain.com │
│ ADMIN_PASSWORD : (gesetzt, 20 Zeichen) │
│ NEXTAUTH_URL : https://your-domain.com │
│ DATABASE_URL : file:/app/data/prod.db │
│ data/ owner : www-data (33) │
└─────────────────────────────────────────────────────┘
Prüft hier, ob alle Werte wie erwartet geladen wurden. Falls ADMIN_USERNAME als (nicht gesetzt → admin) angezeigt wird, wurde die Variable nicht korrekt übergeben.
Traefik / Reverse Proxy (optional)
Wer Traefik als Reverse Proxy verwendet, kann die Labels direkt im Compose-File setzen. Entfernt dabei den ports-Block und ersetzt ihn durch Labels:
services:
chastitytracker:
image: ghcr.io/trublue-2/chastitytracker:latest
container_name: kg-yourname
restart: unless-stopped
environment:
NEXTAUTH_SECRET: "your-secret-here"
NEXTAUTH_URL: "https://yourname.your-domain.com"
volumes:
- ./data:/app/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.chastitytracker.rule=Host(`yourname.your-domain.com`)"
- "traefik.http.routers.chastitytracker.entrypoints=websecure"
- "traefik.http.routers.chastitytracker.tls.certresolver=letsencrypt"
- "traefik.http.services.chastitytracker.loadbalancer.server.port=3000"
networks:
- traefik
networks:
traefik:
external: true
Passt yourname.your-domain.com sowie den Netzwerknamen an eure Traefik-Konfiguration an. TLS wird über Let's Encrypt automatisch ausgehandelt, sofern euer Traefik-Setup einen letsencrypt-Certresolver kennt.
Andere Reverse Proxies (nginx, Caddy) funktionieren ebenfalls — leitet in dem Fall einfach Port 3000 des Containers weiter und setzt den NEXTAUTH_URL entsprechend.
Image-Tags: Release-Kanäle und Version pinnen
Das Docker-Image wird unter mehreren Tags veröffentlicht. Welchen ihr in der docker-compose.yml eintragt, entscheidet darüber, wie oft und wie kontrolliert sich euer Stand verändert. Der offizielle Release liegt auf ghcr.io/trublue-2/chastitytracker:latest — das ist die Empfehlung für den eigenen Server.
| Tag | Bedeutung | Für wen |
|---|---|---|
:latest | Der offizielle, freigegebene Release. Bewegt sich nur bei einer bewussten Freigabe — dabei wird kein neues Image gebaut, sondern ein bereits erprobtes umgehängt. | Self-Hoster (Empfehlung) |
:v4.56.1 usw. | Eine feste Versionsnummer, unveränderlich. Zeigt für immer auf dasselbe Image. | Wer den Zeitpunkt jedes Updates selbst bestimmen will |
:portal, :feature | Interne Kanäle des Betreibers. Folgen dem Entwicklungsstand ohne jedes Stabilitätsversprechen. | Nicht für Self-Hoster gedacht |
Das ist eine Änderung gegenüber früher: bisher landete jeder Commit sofort auf :latest, ein docker compose pull konnte also jederzeit einen ungetesteten Stand ziehen. Ab jetzt bewegt sich :latest nur bei einer bewussten Freigabe — und zwar auf ein Image, das vorher schon im Betrieb gelaufen ist. Eine Freigabe ist damit ein Erfahrungswert, keine Zusicherung: Support, Garantien oder Reaktionszeiten gibt es für keinen der Kanäle.
Wer mit einem Update-Automatismus wie Watchtower arbeitet, sollte das im Kopf behalten: auf :latest zieht der jeden freigegebenen Release automatisch nach. Wer das nicht will, pinnt eine Version.
Auf eine feste Version pinnen
Statt :latest eine konkrete Versionsnummer in die docker-compose.yml eintragen:
services:
chastitytracker:
image: ghcr.io/trublue-2/chastitytracker:v4.56.1
Dieses Tag ändert sich nie von selbst. Ein Update ist dann ein bewusster Schritt: Versionsnummer hochziehen, docker compose pull, docker compose up -d. Welche Versionen es gibt, steht im Changelog der App und im GitHub-Repository.
Rollback auf eine ältere Version
Weil die Versions-Tags unveränderlich sind, ist ein Rollback ein Rückschritt auf eine Zeile in der Compose-Datei: alte Versionsnummer eintragen, docker compose pull && docker compose up -d. Achtung — das gilt nur für die Anwendung, nicht für die Datenbank: hat eine neuere Version das Schema migriert, braucht der Rollback das Backup des ./data-Verzeichnisses von vor dem Update. Ein weiterer Grund, dieses Backup wirklich anzulegen.
Update-Hinweis in der App
Der Hinweis „neue Version verfügbar" in der Kopfzeile richtet sich nach dem freigegebenen Release. Er meldet damit keine Version mehr, für die es noch kein Image gibt. Wer eine eigene Changelog-Quelle nutzen möchte, findet die Variablen DISABLE_UPDATE_CENSUS und UPSTREAM_CHANGELOG_URL in docs/update-check.md.
Updates einspielen
Um auf eine neue Version zu aktualisieren:
docker compose pull
docker compose up -d
Docker zieht das neue Image und startet den Container neu. Die Datenbank im Volume bleibt dabei unverändert. Lest vor jedem Update die Release-Notes im GitHub-Repository, um auf breaking changes oder notwendige Migrationsschritte hingewiesen zu werden.
Ein automatisches Backup des ./data-Verzeichnisses vor jedem Update ist empfehlenswert.
Bei Fragen oder Problemen schaut zunächst in die GitHub Issues. Wer keinen eigenen Server betreiben möchte, kann eine verwaltete Instanz über das Self-Service-Portal erstellen.