Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 25.09.2026 · 11 min Lesezeit Geprüft 02.11.2026

Termix mit Docker Compose selbst hosten: SSH-Verwaltung im Browser

Termix bündelt SSH-Terminal, SFTP, Tunnel und Remote Desktop in einer selbst gehosteten Weboberfläche. Die Anleitung zeigt Docker Compose, Erstkonfiguration, TLS, Backup und Restore sowie Updates, getestet mit Version 26.11.0.

Werbung Mit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Illustration mit der Überschrift Termix selbst hosten mit Docker Compose, drei Karten zu SSH, Sicherheit und Backup sowie ein abstraktes Architekturdiagramm mit Container und Servern

Termix bündelt SSH-Terminal, SFTP, Tunnel, Docker-Verwaltung, Host-Metriken und über den Guacamole-Daemon guacd auch RDP, VNC und Telnet in einer selbst gehosteten Weboberfläche.

Geprüft wurde Termix 26.10.1 am 14.10.2026 samt Update von 2.8.0, am 02.11.2026 zusätzlich das Update auf 26.11.0. Vorab zwei Befunde: Die Registrierung bleibt nach dem ersten Konto offen, und ein Backup ohne .env ergibt eine unlesbare Datenbank.

Was Termix ist: Nutzen und Grenzen

Termix ist eine Node.js-Anwendung hinter internem nginx (26.10.1 vom 12.10.2026, Apache-2.0, keine Bezahlstufen). Hosts, Zugangsdaten und Einstellungen liegen in einer SQLite-Datenbank, laut Dokumentation als ganze Datei mit AES-256-GCM verschlüsselt, sensible Felder zusätzlich mit passwortabgeleitetem Schlüssel.

Wofür Termix sinnvoll ist:

  • Zentrale Hostsammlung mit Ordnern, Tags und wiederverwendbaren Zugangsdaten
  • Zugriff von wechselnden Geräten ohne lokale SSH-Clients
  • Teams: Hosts teilen mit Benutzern oder Rollen, derzeit nur Nur-Ansicht
  • Gelegentliche RDP- und VNC-Sitzungen über guacd

Grenzen und Risiken:

  • Termix speichert Passwörter und SSH-Schlüssel Ihrer Server, eine kompromittierte Instanz ist ein Generalschlüssel.
  • Die Oberfläche spricht nur HTTP, das Passwort geht laut Doku im Klartext im Request-Body: Alles hängt an TLS davor. Den anonymen Nutzungsbericht (Plugin „Usage Statistics“) schalten Sie unter Plugins ab.

Für reine Windows-Desktops im Browser ist Guacamole selbst die bessere Wahl.

Voraussetzungen und Ressourcen

  • Linux-Host mit Docker Engine und Compose-Plugin (docker compose)
  • Laut Benchmark-Seite reichen für 1 bis 25 Hosts 1 Kern, 512 MB RAM und 1 GB Platte, für bis 100 Hosts 1 Kern und 1 GB RAM, für Remote Desktop zusätzlich 1 Kern und 1 GB
  • Im Leerlauf: Termix 225,8 MiB RAM, 0,14 Prozent CPU, guacd 10,5 MiB
  • Images: Termix 26.10.1 rund 1 GB, guacd 377 MB entpackt
  • DNS-Name und Reverse Proxy mit TLS oder VPN, bevor die Instanz SSH-Zugangsdaten aufnimmt
  • Ausgehender Zugriff auf SSH (Port 22) bzw. RDP/VNC der Ziele

Unterstützte Plattformen und getestete Version

Das Image liegt unter ghcr.io/termix-ssh/termix, für linux/amd64 und linux/arm64. Seit 26.10 gelten Kalenderversionen.

KomponenteGetestete VersionHinweis
Termixghcr.io/termix-ssh/termix:26.11.0Log: Termix Backend starting, Version: 26.11.0
guacdguacamole/guacd:1.6.0Log: guacd version 1.6.0 started
DatenbankSQLite (Standard)Datei db.sqlite.encrypted im Volume

Image-Tags tragen kein v (26.11.0, nicht v26.11.0).

compose.yaml und .env

Grundlage ist docker/docker-compose.yml aus dem Repository, nicht das README-Beispiel (es veröffentlicht den guacd-Port 4822 unnötig). Geändert: Version gepinnt, Port an localhost, feste Containernamen entfernt, Registrierung per Variable gesperrt.

# Arbeitsverzeichnis anlegen
$ sudo mkdir -p /opt/termix
$ cd /opt/termix

Die Datei /opt/termix/compose.yaml:

name: termix
services:
  termix:
    image: ghcr.io/termix-ssh/termix:${TERMIX_VERSION:-26.11.0}
    restart: unless-stopped
    ports:
      # nur lokal, TLS übernimmt der Reverse Proxy
      - "${TERMIX_BIND:-127.0.0.1}:${TERMIX_PORT:-8080}:8080"
    volumes:
      - termix-data:/app/data
    environment:
      PORT: "8080"
      GUACD_HOST: "guacd"
      GUACD_TUNNEL_HOST: "termix"
      GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole"
      GUACD_DRIVE_PATH: "/termix-data/rdp-drive"
      # Selbstregistrierung dauerhaft sperren (siehe Erstkonfiguration)
      ALLOW_REGISTRATION: "false"
    depends_on:
      - guacd
    networks:
      - termix-net

  guacd:
    image: guacamole/guacd:1.6.0
    restart: unless-stopped
    # kein Port nach außen, Termix spricht guacd intern an
    volumes:
      - termix-data:/termix-data
    networks:
      - termix-net

volumes:
  termix-data:

networks:
  termix-net:

Die /opt/termix/.env enthält keine Geheimnisse.

# /opt/termix/.env
TERMIX_VERSION=26.11.0
TERMIX_BIND=127.0.0.1
TERMIX_PORT=8080

Ohne Remote Desktop entfallen laut README guacd, das Netz und GUACD_*, dazu ENABLE_GUACAMOLE: "false".

docker compose config -q im Verzeichnis /opt/termix gibt nichts aus.

Parameter und Dateipfade erklärt

ParameterBedeutung
PORT: "8080"Port des internen nginx, nicht im Bereich 30001 bis 30005 (Backend-Dienste).
termix-data:/app/dataAlle Daten: Datenbank, .env mit Schlüsseln, Uploads, Zertifikate.
termix-data:/termix-data bei guacdDasselbe Volume für Aufzeichnungen und RDP-Laufwerke.
GUACD_HOST, GUACD_TUNNEL_HOSTServicename von guacd und Rückweg zu Termix.
GUACD_RECORDING_PATH, GUACD_DRIVE_PATHPfade in guacd für Aufzeichnungen und RDP-Laufwerke.
ALLOW_REGISTRATIONfalse überschreibt den Registrierungsschalter, neue Konten erhalten HTTP 403.
PUID, PGIDStandard 1000, App läuft als node.

Installation und Start

$ docker compose config -q

$ docker compose up -d
# Start verfolgen
$ docker compose logs -f termix

Erststart-Log:

Setting up user permissions (PUID: 1000, PGID: 1000)...
SSL disabled - using HTTP-only configuration (default)
[INFO] Termix Backend starting - Version: 26.10.1 [op:startup]
[SUCCESS] JWT secret auto-generated and saved to .env [op:jwt_auto_generated]
[SUCCESS] Database key auto-generated and saved to .env [op:db_key_auto_generated]
[SUCCESS] Encryption key auto-generated and saved to .env [op:encryption_key_auto_generated]
[INFO] Loaded 34 plugin(s) [op:plugin_init]
[SUCCESS] Termix backend started successfully [op:backend_init_complete,duration:4743ms]

Das Log enthält Termix backend started successfully, docker compose ps zeigt beide Container als Up.

Erstkonfiguration: Admin-Konto und Registrierung abschalten

Bei lokaler Bindung: ssh -L 8080:127.0.0.1:8080 admin@server, dann http://localhost:8080. Ohne Konto antwortet /users/setup-required mit {"setup_required":true}, das erste Konto wird Administrator.

Erstes Konto bei gesperrter Registrierung: Mit ALLOW_REGISTRATION: "false" lässt sich auch das erste Konto nicht anlegen. Mit 26.10.1 und 26.11.0 meldete /users/setup-required zwar {"setup_required":true}, die Registrierung antwortete aber mit HTTP 403 Registration is currently disabled. Kommentieren Sie die Zeile ALLOW_REGISTRATION für den ersten Start deshalb aus (# davor), starten Sie mit docker compose up -d, legen Sie das Admin-Konto an, entfernen Sie das # wieder und führen Sie docker compose up -d erneut aus. Danach meldet /users/registration-allowed {"allowed":false}, die Anmeldung mit dem Admin-Konto funktioniert, neue Konten erhalten HTTP 403.

Achtung, Testbefund: Ohne ALLOW_REGISTRATION bleibt die Registrierung nach dem ersten Konto offen, auch in 26.10.1 ließ sich ein zweites Konto ohne Anmeldung anlegen. Legen Sie das Admin-Konto an, bevor der Reverse Proxy Termix erreichbar macht, und schalten Sie die Registrierung sofort ab.

  • In der Oberfläche: Admin-Einstellungen über Ihren Benutzernamen unten links, dort die Registrierung deaktivieren.
  • Per PATCH /users/registration-allowed mit {"allowed":false}, danach HTTP 403 bei neuen Registrierungen.
  • Dauerhaft: ALLOW_REGISTRATION: "false" in der compose.yaml (wie oben), danach HTTP 403 Registration is currently disabled. Das erste Konto legen Sie vorher bei auskommentierter Zeile an (siehe oben).
  • Aktivieren Sie TOTP für das Admin-Konto, Anmeldeversuche sind laut Doku begrenzt (Sperre 10 Minuten). Falsche Passwörter erscheinen als [WARN] Login failed: incorrect password im Log (geeignet für Fail2ban).

curl -s http://127.0.0.1:8080/users/registration-allowed liefert {"allowed":false}.

Funktions- und Healthcheck

Das Image bringt einen Healthcheck mit, /health ist von außen HTTP 404 (kein Fehler). Danach zeigt docker compose ps Up 2 minutes (healthy), guacd bleibt wegen 300-Sekunden-Intervall lange auf health: starting.

# Status, termix muss "healthy" zeigen
$ docker compose ps
# Healthcheck im Container (HTTP/1.1 200 OK)
$ docker compose exec termix wget -S -qO- http://localhost:30001/health
# Weboberfläche vom Host
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
# Einrichtung nötig? Registrierung erlaubt?
$ curl -s http://127.0.0.1:8080/users/setup-required
$ curl -s http://127.0.0.1:8080/users/registration-allowed

termix zeigt (healthy), die Oberfläche liefert 200, setup-required nach dem Anlegen des Admins false.

Persistente Daten und Rechte

Im Volume /app/data (Eigentümer node, 1000:1000):

-rw------- node node    392 .env
-rw-r--r-- node node 827632 db.sqlite.encrypted
drwxr-xr-x node node        uploads

Die .env enthält JWT_SECRET, DATABASE_KEY, ENCRYPTION_KEY und INTERNAL_AUTH_TOKEN, ohne DATABASE_KEY ist die Datenbank nicht lesbar. Nach dem ersten Neustart legte Termix zusätzlich backups/pre-database-layer-refactor-<Zeitstempel>/ mit Kopie der .env an (Modus 644).

Wer das Volume lesen kann (root, Gruppe docker), kann die Datenbank entschlüsseln. Docker-Rechte nur für Administratoren, Backups wie Passworttresore behandeln. Die Datenbank liegt im Speicher und wird periodisch geschrieben: Stoppen Sie für Backups sauber.

docker compose exec termix ls -l /app/data zeigt .env und db.sqlite.encrypted mit Eigentümer node.

Netzwerkfreigabe, Reverse Proxy und TLS

Sicherheitswarnung: Termix hält SSH-Schlüssel und Passwörter Ihrer Server. Veröffentlichen Sie Port 8080 nie direkt und nie ohne TLS, sondern nur hinter einem Reverse Proxy mit gültigem Zertifikat, idealerweise zusätzlich per VPN. Legen Sie das Admin-Konto an und sperren Sie die Registrierung, bevor Sie den Proxy freischalten.

Laut offizieller Doku müssen WebSockets weitergeleitet werden, sonst brechen Terminals ab. nginx:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Caddy leitet WebSockets selbst weiter (reverse_proxy localhost:8080). Traefik: loadbalancer.server.port=8080. Unterpfad: VITE_BASE_PATH und BASE_PATH. OIDC: OIDC_FORCE_HTTPS=true. Eigenes HTTPS: ENABLE_SSL=true.

Die Trusted-Proxy-Authentifizierung (TRUSTED_PROXY_AUTH_ENABLED) übernimmt Benutzer und Rolle aus HTTP-Headern. Aktivieren Sie sie nur, wenn der Container nur über den Proxy erreichbar und TRUSTED_PROXY_AUTH_TRUSTED_PROXIES eng gesetzt ist, sonst genügen gefälschte Header für Admin-Rechte.

curl -I https://termix.example.de antwortet über TLS mit gültigem Zertifikat, eine Terminal-Sitzung bleibt über den Proxy bestehen, ss -tlnp | grep 8080 zeigt nur 127.0.0.1:8080.

Backup und Restore

Gesichert wird das Volume termix_termix-data samt .env bei gestopptem Stack.

# 1. Stack sauber anhalten
cd /opt/termix
docker compose stop

# 2. Volume sichern (nur lesend)
mkdir -p /srv/backup/termix
docker run --rm -v termix_termix-data:/data:ro -v /srv/backup/termix:/backup alpine:latest \
  tar czf /backup/termix-data-$(date +%F).tar.gz -C /data .

# 3. Stack wieder starten
docker compose start

# 4. Archiv schützen (enthält den Datenbankschlüssel)
sudo chmod 600 /srv/backup/termix/termix-data-*.tar.gz

Das Archiv enthielt ./.env und ./db.sqlite.encrypted. Verschlüsseln Sie es vor der Ablage außerhalb des Hosts.

Restore auf leeren Zustand (Test: Volume vorher mit docker compose down -v gelöscht):

# 1. Leeres Volume anlegen
docker volume create termix_termix-data

# 2. Archiv einspielen, Eigentümer node (1000:1000)
docker run --rm -v termix_termix-data:/data -v /srv/backup/termix:/backup:ro alpine:latest \
  sh -c 'tar xzf /backup/termix-data-2026-09-25.tar.gz -C /data && chown -R 1000:1000 /data'

# 3. Stack starten
docker compose up -d

Nach rund 21 Sekunden war die Anmeldung mit dem alten Passwort möglich, Daten und gesperrte Registrierung stimmten.

Schlüsselverlust (getestet): Ohne .env erzeugt Termix neue Schlüssel und scheitert an der alten Datenbank:

[SUCCESS] Database key auto-generated and saved to .env
[ERROR] Database decryption authentication failed - possible causes: wrong DATABASE_KEY, corrupted files, or interrupted write
Error: Database decryption authentication failed. This usually means:
1. DATABASE_KEY has changed or is missing from /app/data/.env
[ERROR] Failed to initialize backend services [op:startup_failed]

Der Container hing in einer Neustartschleife (API: nginx-404). Mit der originalen .env startete Termix normal, ohne sie gibt es keinen Rückweg.

Der Hosts-Export der Oberfläche ist unverschlüsselt, das Volume-Backup ist robuster.

tar tzf auf das Archiv listet ./.env und ./db.sqlite.encrypted. Nach einem Test-Restore ist die Anmeldung mit dem bisherigen Admin-Passwort möglich.

Updates und Rollback

# 1. Backup wie oben erstellen

# 2. Neue Version in .env eintragen, zum Beispiel
#    TERMIX_VERSION=26.11.1
$ nano /opt/termix/.env
# 3. Image laden und Container neu erstellen
$ docker compose pull
$ docker compose up -d
# 4. Version und Start im Log prüfen
$ docker compose logs termix | grep "Version:"

Lesen Sie vor jedem Update die Release-Notes und updaten Sie zeitnah.

Update von 26.10 auf 26.11: Laut Release Notes speichert 26.11.0 die Datenbank im Hintergrund, ohne den Server währenddessen zu blockieren, und schreibt große Datenbanken seltener; das behebt langsame Anfragen und abgerissene Verbindungen. Eine einzelne langsame Anfrage führt nicht mehr zur Meldung „Server connection lost“, und Hosts mit SSH-Schlüssel und Passwort melden sich jetzt auch an Servern mit AuthenticationMethods publickey,password an. Neue Variablen oder Migrationsschritte nennt das Projekt nicht. Beim Wechsel von 26.10.1 auf 26.11.0 mit dieser Compose-Datei genügte der neue Wert in der .env: Das Log meldete Version: 26.11.0, der Container war healthy, Admin-Anmeldung 200, falsches Passwort 401, Registrierung weiter mit 403 gesperrt. Auch das unter 26.10.1 erstellte Volume-Backup ließ sich unter 26.11.0 zurückspielen, die Anmeldung funktionierte danach.

Update von 2.8 auf 26.10

Ein direkter Sprung endet in einer Neustartschleife: Error: This database is from Termix 2.8 or older. Upgrade to Termix 2.9.1 and start it once so it can move your data, then upgrade to this version.

  1. Volume sichern wie oben.
  2. TERMIX_VERSION=2.9.2, pull, up -d, warten auf Termix backend started successfully.
  3. Image-Namen auf ghcr.io/termix-ssh/termix und TERMIX_VERSION=26.10.1 ändern, erneut pull und up -d.

Dabei blieben Admin-Konto, Host und gesperrte Registrierung erhalten. ENABLE_TELEMETRY entfällt, ein Zurück auf 2.8 ist ausgeschlossen.

Rollback-Grenzen: Termix migriert beim Start das Schema (Schema migration completed), ein älterer Image-Tag ist nicht vorgesehen. Sicherer Rollback: Stack stoppen, Volume löschen, Backup von vor dem Update einspielen, alte Version starten (Änderungen seitdem gehen verloren).

Die Logzeile Termix Backend starting, Version: nennt die neue Version, docker compose ps zeigt (healthy).

Grundlagen zum Betrieb finden Sie in Docker Compose unter Linux: Grundlage für Selfhosting, Docker Compose absichern: Secrets, Healthchecks, Non-Root, Linux-Server absichern mit UFW und Fail2ban und Docker-Container automatisch aktualisieren: Diun, WUD und Renovate im Vergleich.

Typische Fehler mit Diagnose und Lösung

SymptomDiagnoseLösung
Container startet immer neu, Log zeigt Database decryption authentication failed.env fehlt oder enthält einen anderen DATABASE_KEY als beim Anlegen der DatenbankOriginale .env aus dem Backup ins Volume kopieren, Eigentümer 1000:1000, neu starten
Startseite lädt, Anmeldung schlägt mit 404 fehlBackend noch in der Initialisierung (Erststart 53,7 s) oder abgestürzt; docker compose logs termixAuf Termix backend started successfully warten, sonst den Fehler im Log beheben
curl https://.../health liefert 404Der Healthcheck-Pfad ist nur intern auf Port 30001 erreichbardocker compose ps oder docker compose exec termix wget -qO- http://localhost:30001/health nutzen
guacd bleibt lange auf health: startingHealthcheck-Intervall des Images 300 sAbwarten, im Log Listening on ... port 4822 prüfen
Pull scheitert mit not foundTag mit v wie v26.10.1 verwendet (404)Tag 26.10.1 verwenden
Neustartschleife mit This database is from Termix 2.8 or olderSprung von 2.8Erst 2.9.2 starten
Fremde Konten tauchen in der Benutzerliste aufRegistrierung blieb nach dem ersten Konto offen (bestätigt)Registrierung abschalten, unbekannte Konten löschen, Zugangsdaten rotieren
Terminal trennt hinter dem Proxy sofortWebSocket-Upgrade fehlt (laut Doku)Upgrade- und Connection-Header im Proxy setzen
Termix verweigert den Start mit leerem DatenverzeichnisSchutz gegen leeres DATA_DIR bei vorhandener DatenbankVolume-Pfad prüfen; ALLOW_EMPTY_DATA_DIR=true nur für frischen Start

Saubere Deinstallation

Warnung vor Datenverlust: -v löscht das Volume unwiderruflich (Hosts, Zugangsdaten, Schlüssel). Sichern Sie vorher und löschen Sie alte Backups danach sicher, sie enthalten entschlüsselbare Zugangsdaten.

# 1. Container, Netz und Volume entfernen
$ cd /opt/termix
$ docker compose down -v --remove-orphans
# 2. Images entfernen
$ docker rmi ghcr.io/termix-ssh/termix:26.10.1 guacamole/guacd:1.6.0
# 3. Verzeichnis löschen
$ sudo rm -rf /opt/termix
# 4. Kontrolle: keine Reste mehr
$ docker volume ls --filter name=termix
$ docker ps -a --filter name=termix

Entfernen Sie auch die verteilten SSH-Schlüssel aus den authorized_keys Ihrer Server.

Passende Anleitungen auf S-EDV

Quellen

TermixSSHDocker ComposeSelfhostingFernwartungBackupSicherheit