termal.in

Connector · Docker

Betreibe den Connector in einem Container – ohne eingehenden Port.

Das offizielle Image ghcr.io/termalinapp/termalin-connector betreibt den Termalin Connector in einem Container auf einer beliebigen Maschine im Netzwerk – einem NAS, einem Homelab, einem Router, einem Kubernetes-Cluster, einem ECS-Dienst. Er baut die Verbindung nach außen zu Termalin auf und erreicht die Server, internen Websites und Desktops in diesem Netzwerk – ohne einen eingehenden Port zu öffnen. Rund 4 MB, amd64/arm64, läuft als uid 10001.

amd64 / arm64 · kein eingehender Port · Pro (oder 14-tägige Testphase)

01  /  warum ein Container

Ein Container im Netzwerk, nichts zu öffnen.

Ein Connector lässt Termalin eine private Box ohne eingehenden Port und ohne langlebigen Schlüssel darauf erreichen. Manche Maschinen lassen sich leichter als Container hinzufügen, als ein Binary und einen Dienst darauf zu installieren – und manche können nur Container ausführen. Das Image passt für sie:

  • ▸Ein NAS oder Homelab. Synology, Unraid oder ein Docker-Host in deinem LAN – erreiche das SSH der Box selbst und jeden anderen Server, jede Website oder jeden Desktop im Netzwerk.
  • ▸Ein Router. MikroTik RouterOS 7 führt Container aus; einer hält den Connector für den Router und alles dahinter.
  • ▸Kubernetes. Ein Deployment mit einer Replika erreicht Cluster-Dienste über ihre interne Adresse.
  • ▸AWS ECS / eine VPC. Ein Fargate- oder EC2-Task mit ausschließlich ausgehendem HTTPS – kein Load Balancer, keine eingehende Regel – erreicht private Instanzen, interne Websites und Desktops.

02  /  das Image

Vier Megabyte, keine Shell, kein SSH-Server.

Das Image ist ghcr.io/termalinapp/termalin-connector:<version> (und :latest), für linux/amd64 und linux/arm64.

  • ▸Rund 4 MB. Der statische Connector, CA-Zertifikate und sonst nichts – keine Shell, kein Paketmanager, kein SSH-Server. Es läuft als uid 10001 und legt seine Konfiguration im Volume /data ab.
  • ▸Offen gebaut. Im öffentlichen Repository termalinapp/termalin, ausschließlich aus den signierten Release-Binaries auf termal.in – jedes gegen das Release-Manifest und seine Signatur geprüft, bevor es hineinkommt.
  • ▸armv7 wird noch nicht gebaut (32-Bit-ARM). Vorerst nur amd64 und arm64.

03  /  Schnellstart

Binde einen Container mit einem docker run an.

Hol dir im Web-Kabinett ein Anbindungs-Token (Hinzufügen → Connector), dann:

docker run -d --name termalin --restart unless-stopped \
  -v termalin:/data \
  -e TERMALIN_TOKEN=<token> \
  -e TERMALIN_TARGET=192.168.1.10:22 \
  -e TERMALIN_USER=deploy \
  ghcr.io/termalinapp/termalin-connector

Die Variablen:

VariableWas sie macht
TERMALIN_TOKENAnbindungs-Token; wird nur beim ersten Start verwendet, wenn das Volume noch keine Konfiguration hat.
TERMALIN_TARGEThost:port des SSH-Servers, an den Terminals, Befehle und Dateien gehen (der Docker-Host oder eine andere Maschine). Im Container ist 127.0.0.1 der Container selbst, der keinen SSH-Server hat.
TERMALIN_USERLogin-Benutzer auf diesem SSH-Server (Standard: der Standard des Kontos).
TERMALIN_NAMEName im Web-Kabinett (Standard: der Hostname des Containers – setze --hostname oder dies).
TERMALIN_SERVERStandard https://termal.in.

Erster Start: Ohne Konfiguration in /data und mit gesetztem Token bindet sich der Container an, gibt die Anmeldezeile unten aus und verbindet sich. Spätere Starts verbinden sich nur noch – das Token darf in der Umgebung bleiben, es wird nicht erneut gesendet. Ohne Konfiguration und ohne Token sagt er das und beendet sich mit 1. docker stop stoppt ihn sofort. Um erneut anzubinden, entferne das Volume und starte mit einem neuen Token.

04  /  die Anmeldezeile

Dem Zertifikat auf dem Ziel vertrauen.

Der Connector im Container rührt das Ziel nie an. Beim Anbinden gibt er die Zeile aus, mit der sich Termalins kurzlebige Zertifikate für dieses Konto anmelden können – und docker exec termalin termalin-agent check gibt sie erneut aus:

cert-authority,principals="termalin-<account id>" ssh-ed25519 AAAA… termalin-connector

Trag sie in die ~/.ssh/authorized_keys des Login-Benutzers auf dem Ziel ein (eine Zeile, Dateimodus 600, ~/.ssh 700). Auf einem Server, den du als root verwaltest, kannst du stattdessen TrustedUserCAKeys und AuthorizedPrincipalsFile in dessen sshd_config eintragen – check gibt beides aus. Ohne das weist das Ziel Terminals und Befehle über diesen Connector ab; Web-Links und Desktops brauchen sie nicht. check meldet außerdem, ob der SSH-Server des Ziels antwortet (sshd=up); der Anmeldestatus ist ca=unknown, weil die Dateien des Ziels aus dem Container heraus nicht gelesen werden können.

05  /  kein SSH-Server nötig

Interne Websites und Desktops, ganz ohne sshd.

Interne Websites und Remotedesktops (RDP, VNC) laufen nicht über SSH: Das Backend bittet den Connector, eine Verbindung zum eigenen host:port der Website oder des Desktops zu öffnen, und der Connector verbindet sich direkt dorthin. So kann ein Container in einer VPC, einem ECS-Dienst oder einem Heimnetz interne Websites und Desktops ganz ohne SSH-Server bereitstellen. Die Datenbankseite mit einem Datenbankpasswort funktioniert genauso, zum Port der Datenbank.

  • ▸Nur private Adressen. Die Ziele müssen im privaten Netz liegen – RFC 1918, Loopback, Link-Local, CGNAT 100.64/10 (etwa Tailscale), IPv6-ULA. Der Connector ist kein Relay ins Internet, und Cloud-Metadaten-Adressen (169.254.169.254 und dergleichen) werden immer abgelehnt.
  • ▸Braucht einen SSH-Server auf dem Ziel: das Terminal, ssh_exec und die anderen Befehls-Tools, Dateien über SFTP, die passwortlosen Datenbank-Tools, Metriken und Backups. Setze TERMALIN_TARGET und füge dort die Anmeldezeile hinzu.

06  /  docker compose

Dasselbe als Compose-Datei.

services:
  termalin:
    image: ghcr.io/termalinapp/termalin-connector:latest
    restart: unless-stopped
    hostname: office-nas
    environment:
      TERMALIN_TOKEN: ${TERMALIN_TOKEN}
      TERMALIN_TARGET: 192.168.1.10:22
      TERMALIN_USER: deploy
    volumes:
      - termalin:/data
volumes:
  termalin:

Um den Docker-Host selbst zu erreichen, nutze dessen LAN-Adresse oder unter Docker Desktop host.docker.internal:22 (unter Linux extra_hosts: ["host.docker.internal:host-gateway"] ergänzen).

07  /  NAS

Synology und Unraid.

  • ▸Synology (Container Manager): Registry → ghcr.io/termalinapp/termalin-connector hinzufügen, einen Container mit den Variablen oben anlegen, ein Volume für /data, „Automatischer Neustart“ an. Aktiviere SSH unter Systemsteuerung → Terminal & SNMP für das Terminal und setze TERMALIN_TARGET=<NAS-Adresse>:22.
  • ▸Unraid: Docker → Add Container, Repository ghcr.io/termalinapp/termalin-connector, ein Pfad /data → /mnt/user/appdata/termalin, die Variablen als Container-Variablen.
  • ▸Ein per Bind-Mount eingebundener Ordner muss für uid 10001 beschreibbar sein (chown 10001:10001 /mnt/…/termalin); ein benanntes Volume richtet Docker für dich ein.

08  /  MikroTik RouterOS 7

Auf dem Router selbst.

Vorerst arm64- und x86-Router (noch kein armv7-Build). Aktiviere das container-Paket und den Device-Mode, dann:

/interface/veth add name=veth-termalin address=172.17.0.2/24 gateway=172.17.0.1
/interface/bridge/port add bridge=containers interface=veth-termalin
/container/envs add name=termalin key=TERMALIN_TOKEN value=<token>
/container/envs add name=termalin key=TERMALIN_TARGET value=192.168.88.1:22
/container/mounts add name=termalin-data src=/usb1/termalin dst=/data
/container add remote-image=ghcr.io/termalinapp/termalin-connector:latest \
    interface=veth-termalin envlist=termalin mounts=termalin-data \
    root-dir=/usb1/termalin-root start-on-boot=yes logging=yes

Das setzt eine Bridge containers mit 172.17.0.1/24 und NAT nach außen voraus, wie in MikroTiks eigenem Container-Guide. Ziele auf den SSH-Dienst des Routers selbst (/ip/service enable ssh) oder auf einen beliebigen Host dahinter.

09  /  Kubernetes

Eine Replika, ein Secret, ein winziges PVC.

apiVersion: v1
kind: Secret
metadata: { name: termalin }
stringData: { token: "<token>" }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: termalin-data }
spec:
  accessModes: [ReadWriteOnce]
  resources: { requests: { storage: 16Mi } }
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: termalin-connector }
spec:
  replicas: 1
  strategy: { type: Recreate }
  selector: { matchLabels: { app: termalin-connector } }
  template:
    metadata: { labels: { app: termalin-connector } }
    spec:
      securityContext: { runAsUser: 10001, runAsGroup: 10001, fsGroup: 10001, runAsNonRoot: true }
      containers:
        - name: connector
          image: ghcr.io/termalinapp/termalin-connector:0.2.3
          env:
            - { name: TERMALIN_TOKEN, valueFrom: { secretKeyRef: { name: termalin, key: token } } }
            - { name: TERMALIN_TARGET, value: "10.0.12.7:22" }
            - { name: TERMALIN_NAME, value: "k8s-prod" }
          securityContext: { readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, capabilities: { drop: [ALL] } }
          resources: { requests: { cpu: 10m, memory: 16Mi }, limits: { memory: 64Mi } }
          volumeMounts: [{ name: data, mountPath: /data }]
      volumes:
        - { name: data, persistentVolumeClaim: { claimName: termalin-data } }

Interne Dienste erreichst du über ihre Cluster-Adresse in einem Web-Link – http://grafana.monitoring.svc:3000 löst über das Cluster-DNS zu einer privaten Adresse auf. Halte eine Replika pro Anbindung: zwei Pods auf demselben Volume wären derselbe Connector zweimal.

10  /  AWS ECS

Ein Fargate- oder EC2-Task, nur ausgehend.

Ein Service mit desired count 1, dem Image oben, dem Token aus dem Secrets Manager als TERMALIN_TOKEN und einem EFS-Volume unter /data (im Besitz von 10001 – ein EFS-Access-Point mit uid/gid 10001). Mehr als ausgehendes HTTPS zu termal.in braucht er nicht – kein Load Balancer, keine eingehende Regel. Die Security-Groups der Ziele müssen den Task auf den genutzten Ports hereinlassen (22, 3389, die Ports der Websites). Die Metadaten-Adresse des ECS-Tasks wird abgelehnt wie jede Metadaten-Adresse.

11  /  Updates und Grenzen

Zieh ein neues Tag; was ein Container nicht kann.

  • ▸Updates. Der Container aktualisiert sich nie selbst – zieh ein neues Tag und erstelle ihn neu (docker compose pull && docker compose up -d, oder ein neues Image-Tag im Deployment). Das Volume behält die Anbindung. Das Web-Kabinett zeigt dafür kein „Jetzt aktualisieren“.
  • ▸SSH-Funktionen brauchen ein Ziel. Terminal, Befehle, Dateien, passwortlose Datenbanken, Metriken und Backups brauchen einen SSH-Server auf dem Ziel und dort die Anmeldezeile; das Image hat selbst keinen.
  • ▸Ein Ziel-SSH-Server pro Connector (TERMALIN_TARGET); Web-Links und Desktops können jede private Adresse angeben.
  • ▸armv7 (32-Bit-ARM) wird noch nicht gebaut.

Fragen

Welche Architekturen, und wie groß ist das Image?

Ein Multi-Arch-Image für linux/amd64 und linux/arm64, rund 4 MB groß: der statische Connector, CA-Zertifikate und sonst nichts – keine Shell, kein Paketmanager, kein SSH-Server. Es läuft als uid 10001 und legt seine Konfiguration im Volume /data ab. armv7 (32-Bit-ARM) wird noch nicht gebaut.

Woher stammt das Image?

Es wird offen gebaut, im öffentlichen Repository termalinapp/termalin, ausschließlich aus den signierten Release-Binaries, die auf termal.in veröffentlicht sind – jedes gegen das Release-Manifest und seine Signatur geprüft, bevor es ins Image kommt.

Brauche ich einen SSH-Server im Container oder daneben?

Nicht für interne Websites, Remotedesktops (RDP/VNC) oder die Datenbankseite – die gehen über den Connector direkt an den eigenen host:port des Ziels, sodass ein Container in einer VPC oder einem Heimnetz sie ganz ohne sshd bereitstellen kann. Ein Terminal, Befehle, SFTP-Dateien, die passwortlosen Datenbank-Tools, Metriken und Backups brauchen hingegen einen SSH-Server auf dem Ziel (TERMALIN_TARGET) und die dort hinzugefügte Anmeldezeile – das Image selbst hat keinen.

Wie aktualisiere ich den Container?

Der Container aktualisiert sich nie selbst. Zieh ein neues Tag und erstelle ihn neu – docker compose pull && docker compose up -d, oder ein neues Image-Tag im Deployment. Das Volume /data behält die Anbindung, du bindest also nicht erneut an. Das Web-Kabinett zeigt für einen Container kein „Jetzt aktualisieren“.

Was kostet es, und welchen Tarif brauche ich?

Der Connector ist eine gehostete Funktion und braucht daher ein Termalin-Konto mit Pro oder der 14-tägigen Testphase. Das Image lässt sich kostenlos ziehen; wie viele Connectors du hinzufügst, ist nicht begrenzt.

Erreiche das ganze Netzwerk aus einem Container.

Ein Token, ein docker run, kein eingehender Port. Auf einem NAS, einem Router, in Kubernetes oder ECS.

Pro (oder 14-tägige Testphase) · Interne Websites · Remotedesktops