termal.in

Connector · Docker

Ejecuta el Connector en un contenedor — sin puerto de entrada.

La imagen oficial ghcr.io/termalinapp/termalin-connector ejecuta el Termalin Connector en un solo contenedor en cualquier máquina de una red — un NAS, un laboratorio casero, un router, un clúster de Kubernetes, un servicio de ECS. Marca hacia fuera a Termalin y llega a los servidores, sitios internos y escritorios de esa red, sin ningún puerto de entrada abierto. Unos 4 MB, amd64/arm64, corre como uid 10001.

amd64 / arm64 · sin puerto de entrada · Pro (o los 14 días de prueba)

01  /  por qué un contenedor

Un contenedor en la red, nada que abrir.

Un Connector permite a Termalin llegar a un equipo privado sin puerto de entrada y sin ninguna clave de larga duración en él. Algunas máquinas son más fáciles de añadir como contenedor que instalándoles un binario y un servicio — y otras solo pueden ejecutar contenedores. La imagen encaja con ellas:

  • ▸Un NAS o laboratorio casero. Synology, Unraid o un host Docker en tu LAN — llega al propio SSH del equipo y a cualquier otro servidor, sitio o escritorio de la red.
  • ▸Un router. MikroTik RouterOS 7 ejecuta contenedores; uno alberga el Connector para el router y todo lo que hay tras él.
  • ▸Kubernetes. Un Deployment de una sola réplica llega a los servicios del clúster por su dirección interna.
  • ▸AWS ECS / una VPC. Una tarea de Fargate o EC2 con solo HTTPS saliente — sin balanceador de carga, sin regla de entrada — llega a instancias privadas, sitios internos y escritorios.

02  /  la imagen

Cuatro megabytes, sin shell, sin servidor SSH.

La imagen es ghcr.io/termalinapp/termalin-connector:<version> (y :latest), para linux/amd64 y linux/arm64.

  • ▸Unos 4 MB. El Connector estático, los certificados de CA y nada más — sin shell, sin gestor de paquetes, sin servidor SSH. Corre como uid 10001 y guarda su configuración en el volumen /data.
  • ▸Compilada a la vista. En el repositorio público termalinapp/termalin, solo a partir de los binarios de versión firmados en termal.in — cada uno comprobado contra el manifiesto de la versión y su firma antes de entrar.
  • ▸armv7 aún no se compila (ARM de 32 bits). Por ahora solo amd64 y arm64.

03  /  inicio rápido

Da de alta un contenedor con un solo docker run.

Consigue un token de alta en el panel web (Añadir → Connector), y luego:

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

Las variables:

VariableQué hace
TERMALIN_TOKENToken de alta; se usa solo en el primer arranque, cuando el volumen aún no tiene configuración.
TERMALIN_TARGEThost:port del servidor SSH al que van los terminales, los comandos y los archivos (el host de Docker, u otra máquina). Dentro del contenedor, 127.0.0.1 es el propio contenedor, que no tiene servidor SSH.
TERMALIN_USERUsuario de inicio de sesión en ese servidor SSH (por defecto: el predeterminado de la cuenta).
TERMALIN_NAMENombre en el panel web (por defecto: el hostname del contenedor — fíjalo con --hostname o con esto).
TERMALIN_SERVERPor defecto https://termal.in.

Primer arranque: sin configuración en /data y con un token definido, el contenedor se da de alta, imprime la línea de acceso de abajo y conecta. Los arranques posteriores solo conectan — el token puede quedarse en el entorno, no se vuelve a enviar. Sin configuración y sin token lo indica y sale con código 1. docker stop lo detiene de inmediato. Para darlo de alta otra vez, elimina el volumen y arranca con un token nuevo.

04  /  la línea de acceso

Confía en el certificado en el objetivo.

El Connector del contenedor nunca toca el objetivo. Al darse de alta imprime la línea que permite iniciar sesión a los certificados de corta duración de Termalin de esta cuenta — y docker exec termalin termalin-agent check la imprime de nuevo:

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

Ponla en ~/.ssh/authorized_keys del usuario de inicio de sesión en el objetivo (una línea, el archivo en modo 600, ~/.ssh en 700). En un servidor que gestionas como root puedes añadir en su lugar TrustedUserCAKeys y AuthorizedPrincipalsFile a su sshd_config — check imprime ambos. Sin esto, el objetivo rechaza los terminales y comandos a través de este Connector; los enlaces web y los escritorios no la necesitan. check también informa de si el servidor SSH del objetivo responde (sshd=up); el estado de acceso es ca=unknown, porque los archivos del objetivo no pueden leerse desde dentro del contenedor.

05  /  sin necesidad de servidor SSH

Sitios internos y escritorios, sin ningún sshd.

Los sitios internos y los escritorios remotos (RDP, VNC) no pasan por SSH: el backend le pide al Connector que abra una conexión al host:port propio del sitio o del escritorio, y el Connector conecta ahí directamente. Así, un contenedor en una VPC, un servicio de ECS o una red doméstica puede publicar sitios internos y escritorios sin ningún servidor SSH. La página de bases de datos con una contraseña de base de datos funciona igual, hacia el puerto de la base de datos.

  • ▸Solo direcciones privadas. Los objetivos a los que marca deben estar en la red privada — RFC 1918, loopback, link-local, CGNAT 100.64/10 (como Tailscale), ULA de IPv6. El Connector no es un relé hacia internet, y las direcciones de metadatos de la nube (169.254.169.254 y similares) se rechazan siempre.
  • ▸Necesita un servidor SSH en el objetivo: el terminal, ssh_exec y las demás herramientas de comandos, los archivos por SFTP, las herramientas de base de datos sin contraseña, la monitorización y las copias de seguridad. Define TERMALIN_TARGET y añade ahí la línea de acceso.

06  /  docker compose

Lo mismo, como archivo 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:

Para llegar al propio host de Docker, usa su dirección de LAN, o en Docker Desktop host.docker.internal:22 (en Linux añade extra_hosts: ["host.docker.internal:host-gateway"]).

07  /  NAS

Synology y Unraid.

  • ▸Synology (Container Manager): Registry → añade ghcr.io/termalinapp/termalin-connector, crea un contenedor con las variables de arriba, un volumen para /data y «auto restart» activado. Activa SSH en Control Panel → Terminal & SNMP para el terminal, y define TERMALIN_TARGET=<dirección del NAS>:22.
  • ▸Unraid: Docker → Add Container, repositorio ghcr.io/termalinapp/termalin-connector, una ruta /data → /mnt/user/appdata/termalin, las variables como variables de contenedor.
  • ▸Una carpeta montada por bind debe poder escribirla uid 10001 (chown 10001:10001 /mnt/…/termalin); un volumen con nombre lo prepara Docker por ti.

08  /  MikroTik RouterOS 7

En el propio router.

Routers arm64 y x86 por ahora (aún no hay compilación armv7). Activa el paquete container y el device-mode, y luego:

/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

Eso da por hecho un bridge containers con 172.17.0.1/24 y NAT de salida, como en la propia guía de contenedores de MikroTik. Apunta al SSH del propio router (/ip/service enable ssh) o a cualquier host tras él.

09  /  Kubernetes

Una réplica, un Secret, un PVC diminuto.

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 } }

A los servicios internos se llega por su dirección de clúster en un enlace web — http://grafana.monitoring.svc:3000 se resuelve por el DNS del clúster a una dirección privada. Mantén una réplica por alta: dos pods sobre el mismo volumen serían el mismo Connector dos veces.

10  /  AWS ECS

Una tarea de Fargate o EC2, solo saliente.

Un servicio con desired count 1, la imagen de arriba, el token de Secrets Manager como TERMALIN_TOKEN y un volumen de EFS en /data (propiedad de 10001 — un punto de acceso de EFS con uid/gid 10001). Solo necesita HTTPS saliente a termal.in — sin balanceador de carga, sin regla de entrada. Los security groups de los objetivos deben dejar entrar a la tarea por los puertos que se usen (22, 3389, los puertos de los sitios). La dirección de metadatos de la tarea de ECS se rechaza como cualquier dirección de metadatos.

11  /  actualizaciones y límites

Baja una etiqueta nueva; lo que un contenedor no puede hacer.

  • ▸Actualizaciones. El contenedor nunca se actualiza solo — baja una etiqueta nueva y recréalo (docker compose pull && docker compose up -d, o una nueva etiqueta de imagen en el Deployment). El volumen conserva el alta. El panel web no muestra «Actualizar ahora» para él.
  • ▸Las funciones SSH necesitan un objetivo. El terminal, los comandos, los archivos, las bases de datos sin contraseña, la monitorización y las copias de seguridad necesitan un servidor SSH en el objetivo y la línea de acceso allí; la imagen no tiene ninguno propio.
  • ▸Un servidor SSH objetivo por Connector (TERMALIN_TARGET); los enlaces web y los escritorios pueden indicar cualquier dirección privada.
  • ▸armv7 (ARM de 32 bits) aún no se compila.

Preguntas

¿Qué arquitecturas admite y cuánto ocupa la imagen?

Una imagen multiarquitectura para linux/amd64 y linux/arm64, de unos 4 MB: el Connector estático, los certificados de CA y nada más — sin shell, sin gestor de paquetes, sin servidor SSH. Corre como uid 10001 y guarda su configuración en el volumen /data. armv7 (ARM de 32 bits) aún no se compila.

¿De dónde sale la imagen?

Se compila a la vista, en el repositorio público termalinapp/termalin, solo a partir de los binarios de versión firmados publicados en termal.in — cada uno comprobado contra el manifiesto de la versión y su firma antes de entrar en la imagen.

¿Necesito un servidor SSH dentro o junto al contenedor?

No para los sitios internos, los escritorios remotos (RDP/VNC) ni la página de bases de datos — esos llegan directamente al host:port propio del objetivo a través del Connector, así que un contenedor en una VPC o en una red doméstica puede publicarlos sin ningún sshd. Un terminal, los comandos, los archivos por SFTP, las herramientas de base de datos sin contraseña, la monitorización y las copias de seguridad sí necesitan un servidor SSH en el objetivo (TERMALIN_TARGET) y la línea de acceso añadida allí — la imagen en sí no tiene ninguno.

¿Cómo actualizo el contenedor?

El contenedor nunca se actualiza solo. Baja una etiqueta nueva y recréalo — docker compose pull && docker compose up -d, o una nueva etiqueta de imagen en el Deployment. El volumen /data conserva el alta, así que no vuelves a darlo de alta. El panel web no muestra «Actualizar ahora» para un contenedor.

¿Cuánto cuesta y qué plan necesito?

El Connector es una función alojada, así que necesita una cuenta de Termalin en Pro o los 14 días de prueba. Bajar la imagen es gratis; no hay límite en cuántos Connectors añades.

Llega a toda la red desde un solo contenedor.

Un token, un docker run, sin puerto de entrada. En un NAS, un router, Kubernetes o ECS.

Pro (o los 14 días de prueba) · Sitios internos · Escritorios remotos