termal.in

Connector · Docker

在容器中运行 Connector,无需入站端口

官方镜像 ghcr.io/termalinapp/termalin-connector 把 Termalin Connector 运行在网络中任意一台机器上的单个容器里——NAS、家庭实验室、路由器、Kubernetes 集群或 ECS 服务。它主动向外连接 Termalin,访问该网络内的服务器、内网站点和远程桌面,无需开放任何入站端口。约 4 MB,支持 amd64/arm64,以 uid 10001 运行。

amd64 / arm64 · 无需入站端口 · Pro(或 14 天试用)

01  /  为什么用容器

网络中一个容器,什么都不用开放

Connector 让 Termalin 无需入站端口、也不在机器上留长期密钥就能访问私有机器。有些机器与其安装二进制文件和服务,不如直接作为容器添加——还有些机器只能运行容器。这个镜像正适合它们:

  • ▸NAS 或家庭实验室。局域网里的 Synology、Unraid 或 Docker 主机——访问这台机器自身的 SSH,以及网络中任何其他服务器、站点或桌面。
  • ▸路由器。MikroTik RouterOS 7 能运行容器;一个容器就能为路由器及其后面的一切承载 Connector。
  • ▸Kubernetes。一个单副本的 Deployment,通过内部地址访问集群内的服务。
  • ▸AWS ECS / VPC。一个仅有出站 HTTPS 的 Fargate 或 EC2 任务——没有负载均衡器,没有入站规则——即可访问私有实例、内网站点和远程桌面。

02  /  镜像

四兆字节,没有 shell,没有 SSH 服务器

镜像为 ghcr.io/termalinapp/termalin-connector:<version>(以及 :latest),支持 linux/amd64 和 linux/arm64。

  • ▸约 4 MB。只有静态编译的 Connector、CA 证书,别无其他——没有 shell、没有包管理器、没有 SSH 服务器。它以 uid 10001 运行,并把配置保存在 /data 卷中。
  • ▸公开构建。在公开的 termalinapp/termalin 仓库中,仅使用 termal.in 上带签名的发布二进制文件构建——每个文件在进入镜像前都会对照发布清单及其签名进行校验。
  • ▸armv7 尚未构建(32 位 ARM)。目前仅支持 amd64 和 arm64。

03  /  快速上手

一条 docker run 接入一个容器

在网页控制台里获取接入令牌(Connectors → 添加),然后:

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

各个变量:

变量作用
TERMALIN_TOKEN接入令牌;仅在首次启动、卷中尚无配置时使用。
TERMALIN_TARGET终端、命令和文件所连接的 SSH 服务器的 host:port(Docker 宿主机,或另一台机器)。在容器内部,127.0.0.1 指的是容器自身,而容器里没有 SSH 服务器。
TERMALIN_USER该 SSH 服务器上的登录用户(默认:账户的默认用户)。
TERMALIN_NAME在网页控制台中显示的名称(默认:容器的主机名——通过 --hostname 或此变量设置)。
TERMALIN_SERVER默认为 https://termal.in。

首次启动:/data 中没有配置、且设置了令牌时,容器会完成接入,打印下面的登录授权行并建立连接。之后的启动只是连接——令牌可以留在环境变量中,它不会被再次发送。既没有配置也没有令牌时,它会提示并以状态码 1 退出。docker stop 会立即停止它。要重新接入,删除卷后用新令牌启动。

04  /  登录授权行

在目标主机上信任该证书

容器里的 Connector 从不接触目标主机。接入时,它会打印那一行——让 Termalin 为此账户签发的短期证书得以登录——docker exec termalin termalin-agent check 也会再次打印它:

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

把它放进目标主机上登录用户的 ~/.ssh/authorized_keys(一行,文件权限 600,~/.ssh 为 700)。在你以 root 管理的服务器上,也可以改为在其 sshd_config 中添加 TrustedUserCAKeys 和 AuthorizedPrincipalsFile——check 两种都会打印。没有这一行,通过这个 Connector 打开的终端和命令会被目标主机拒绝;内网站点和远程桌面则不需要它。check 还会报告目标主机的 SSH 服务器是否响应(sshd=up);登录状态显示为 ca=unknown,因为在容器内部无法读取目标主机上的文件。

05  /  无需 SSH 服务器

内网站点和远程桌面,任何地方都不需要 sshd

内网站点和远程桌面(RDP、VNC)不走 SSH:后端请求 Connector 打开一个到站点或桌面自身 host:port 的连接,Connector 直接连到那里。所以 VPC、ECS 服务或家庭网络里的一个容器,完全不用任何 SSH 服务器就能发布内网站点和远程桌面。数据库页面配合数据库密码也以同样方式工作,连到数据库的端口。

  • ▸仅限私有地址。发起连接的目标必须位于私有网络——RFC 1918、环回地址、链路本地地址、CGNAT 100.64/10(例如 Tailscale)、IPv6 ULA。Connector 不是通向互联网的中转,而云元数据地址(169.254.169.254 之类)始终会被拒绝。
  • ▸需要目标主机上有 SSH 服务器:终端、ssh_exec 及其他命令工具、通过 SFTP 传输文件、免密码的数据库工具、指标和备份。设置 TERMALIN_TARGET 并在那里添加登录授权行。

06  /  docker compose

同样的配置,写成 compose 文件

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:

要访问 Docker 宿主机本身,使用它的局域网地址,或者在 Docker Desktop 上使用 host.docker.internal:22(在 Linux 上需添加 extra_hosts: ["host.docker.internal:host-gateway"])。

07  /  NAS

Synology 和 Unraid

  • ▸Synology(Container Manager):在 Registry 中添加 ghcr.io/termalinapp/termalin-connector,用上面的变量创建一个容器,为 /data 挂载一个卷,并开启“自动重启”。要使用终端,请在 Control Panel → Terminal & SNMP 中启用 SSH,并设置 TERMALIN_TARGET=<NAS 地址>:22。
  • ▸Unraid:Docker → Add Container,仓库填 ghcr.io/termalinapp/termalin-connector,添加一个路径 /data → /mnt/user/appdata/termalin,变量作为容器变量填入。
  • ▸绑定挂载的文件夹必须对 uid 10001 可写(chown 10001:10001 /mnt/…/termalin);命名卷则由 Docker 替你设置好。

08  /  MikroTik RouterOS 7

直接在路由器上运行

目前支持 arm64 和 x86 路由器(尚无 armv7 构建)。启用容器软件包(container package)和设备模式(device-mode),然后:

/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

这里假设有一个名为 containers、地址为 172.17.0.1/24 并做了出站 NAT 的网桥,正如 MikroTik 自己的容器指南所述。目标可指向路由器自身的 SSH(/ip/service enable ssh),或其后面的任意一台主机。

09  /  Kubernetes

一个副本、一个 Secret、一个很小的 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 } }

集群内部的服务,用它们的集群地址以网页链接的形式访问——http://grafana.monitoring.svc:3000 会经集群 DNS 解析到一个私有地址。请每个接入保持一个副本:同一个卷上的两个 Pod 等于把同一个 Connector 跑了两遍。

10  /  AWS ECS

一个 Fargate 或 EC2 任务,仅出站

一个期望任务数为 1 的服务,使用上面的镜像,把来自 Secrets Manager 的令牌作为 TERMALIN_TOKEN,并在 /data 挂载一个 EFS 卷(属主为 10001——一个 uid/gid 均为 10001 的 EFS 访问点)。它所需要的只是到 termal.in 的出站 HTTPS——没有负载均衡器,没有入站规则。目标主机的安全组必须允许该任务从所用端口(22、3389、各站点的端口)进入。ECS 任务元数据地址和其他所有元数据地址一样会被拒绝。

11  /  更新与限制

拉取新标签;容器做不到的事

  • ▸更新。容器从不自行更新——拉取一个新标签并重新创建它(docker compose pull && docker compose up -d,或在 Deployment 中换一个新的镜像标签)。卷会保留接入状态。网页控制台不会为它显示“立即更新”。
  • ▸SSH 功能需要一个目标主机。终端、命令、文件、免密码数据库、指标和备份都需要目标主机上有 SSH 服务器,并在那里添加登录授权行;镜像本身不带任何 SSH 服务器。
  • ▸每个 Connector 只对应一个目标 SSH 服务器(TERMALIN_TARGET);内网站点和远程桌面则可以指向任意私有地址。
  • ▸armv7(32 位 ARM)尚未构建。

常见问题

镜像支持哪些架构?有多大?

一个支持 linux/amd64 和 linux/arm64 的多架构镜像,约 4 MB:只有静态编译的 Connector、CA 证书,别无其他——没有 shell、没有包管理器、没有 SSH 服务器。它以 uid 10001 运行,并把配置保存在 /data 卷中。armv7(32 位 ARM)尚未构建。

镜像从何而来?

它在公开的 termalinapp/termalin 仓库中公开构建,仅使用 termal.in 上发布的带签名的发布二进制文件——每个文件在进入镜像前都会对照发布清单及其签名进行校验。

容器里或容器旁边需要一个 SSH 服务器吗?

内网站点、远程桌面(RDP/VNC)和数据库页面不需要——它们通过 Connector 直接连到目标自身的 host:port,所以 VPC 或家庭网络里的一个容器可以在任何地方都没有 sshd 的情况下发布它们。而终端、命令、SFTP 文件、免密码的数据库工具、指标和备份确实需要目标主机(TERMALIN_TARGET)上有一个 SSH 服务器,并在那里添加登录授权行——镜像本身不带。

如何更新容器?

容器从不自行更新。拉取一个新标签并重新创建它——docker compose pull && docker compose up -d,或在 Deployment 中换一个新的镜像标签。/data 卷会保留接入状态,所以你无需再次接入。网页控制台不会为容器显示“立即更新”。

它收费吗?需要哪个套餐?

Connector 是托管功能,所以需要一个 Pro 或 14 天试用的 Termalin 账户。镜像可免费拉取;你添加的 Connector 数量没有上限。

一个容器,访问整个网络

一个令牌,一条 docker run,无需入站端口。在 NAS、路由器、Kubernetes 或 ECS 上。

Pro(或 14 天试用) · 内网站点 · 远程桌面