SSD Nodes Learn 🎉 VPS desde $5.50/mes
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-21

Autohospeda HarnessRouter con una API para agentes

Despliega Codex, Claude Code y Hermes tras una API propia. Incluye Docker, enlace loopback, cambio del login predeterminado y acceso TLS.

Qué elimina HarnessRouter

Autohospedas HarnessRouter Community Edition para colocar una API delante de varios harnesses de agentes en un servidor que administras. Un harness de agente es el programa de línea de comandos que ejecuta un modelo en un bucle: mantiene una sesión, edita archivos, ejecuta comandos y transmite el progreso al componente que solicitó el trabajo. Codex, Claude Code y Hermes cumplen esa función. Cada uno tiene su propia instalación, su propio formato de credenciales y su propia forma de gestionar las sesiones. HarnessRouter ejecuta todos dentro de un único contenedor y coloca delante un único endpoint HTTP, un único inicio de sesión y un único almacén de secretos.

Esa es toda la idea, y conviene explicar claramente el coste. Añades un contenedor, un inicio de sesión, un volumen y un procedimiento de actualización a tu servidor para convertir varios componentes en uno solo. Si actualmente ejecutas un único harness, esta configuración es peor que instalarlo directamente. Ese compromiso se analiza en la última sección. Léela antes de desplegar.

Todo lo que sigue se comprobó con la etiqueta de imagen 0.5.5, descargada el 19 de agosto de 2026. El proyecto publica etiquetas nuevas casi todos los días. Comprueba la etiqueta que realmente ejecutas en lugar de confiar en esta página dentro de un mes. Los comandos proceden del README del proyecto en github.com/HarnessRouter/harnessrouter.

Qué es realmente el Unified Harness Protocol

HarnessRouter implementa Unified Harness Protocol (UHP), publicado en unifiedharnessprotocol.org. UHP describe cómo un producto inicia una tarea en un harness, supervisa la tarea mientras se ejecuta, administra sesiones y archivos, e informa de los fallos. La especificación utiliza versiones basadas en fechas. La versión vigente el 19 August 2026 está fechada el 2026-08-11, y el sitio la denomina un estándar en fase de borrador, «lo bastante estable para basarse en él y con versiones para poder modificarlo de forma segura».

Lea con atención la expresión «estándar abierto» en este contexto. La misma empresa redacta la especificación, desarrolla la implementación de referencia y mantiene la suite de conformidad de 52 comprobaciones que determina quién cumple el estándar. Esto es habitual en un protocolo tan reciente, y la licencia Apache-2.0 permite bifurcar cualquiera de sus componentes. También significa que UHP todavía no es un estándar con varios proveedores. Trátelo como un protocolo emergente: útil y sujeto a cambios, y asegúrese de que su propio código pueda dejar de usarlo sin tener que reescribirlo.

Qué necesita antes de empezar

Docker y aproximadamente 4 GB de espacio libre en disco. También necesita una clave de API de un proveedor de modelos por el que ya pague. La descarga de la imagen ocupa unos 700 MB. El resto del espacio se utiliza para las CLI de los agentes y los espacios de trabajo donde escriben. La imagen no incluye ningún modelo ni una clave de prueba. Por tanto, las tareas fallan hasta que conecte un proveedor. HarnessRouter utiliza la licencia Apache-2.0. Las CLI de los agentes no están cubiertas por esa licencia. Por eso se descargan durante el primer arranque en lugar de incluirse en la imagen.

Autohospedar HarnessRouter con un solo docker run

docker pull harnessrouter/harnessrouter
docker run -d --name harnessrouter \
  -p 127.0.0.1:3000:3000 \
  -v harnessrouter:/data \
  harnessrouter/harnessrouter

A continuación, supervise cómo se inicia el contenedor. El primer arranque es lento y los registros muestran la causa.

docker logs -f harnessrouter

Mientras se inicia, verá líneas como estas:

installing Claude Code (Anthropic's terms apply)…
installing Codex (Apache-2.0)…
installing Hermes (check its upstream license before use)…

Espere a ready on :3000. La instalación se realiza una vez por volumen, por lo que los arranques posteriores tardan unos segundos y no muestran líneas de instalación.

De esta descarga se derivan dos hechos importantes en un VPS. En primer lugar, el primer arranque necesita acceso de salida a la red. La imagen no es autónoma. Por tanto, un equipo detrás de un filtro de tráfico saliente o sin una ruta de salida se queda bloqueado aquí y nunca muestra ready on :3000. El fallo ocurre durante el primer arranque, no en docker pull, por lo que puede ser difícil detectar la causa. En segundo lugar, está instalando software de terceros bajo las condiciones de terceros. Claude Code se distribuye bajo las condiciones de Anthropic y Hermes bajo las que indique su proyecto de origen. Revise ambas antes de usarlo con fines comerciales.

-v harnessrouter:/data crea un volumen con nombre de Docker. Todos los datos persistentes se guardan en /data: las bases de datos SQLite, los archivos almacenados, el almacén de secretos y los espacios de trabajo de los agentes. Si elimina ese volumen, elimina la instancia, incluidas las claves de los proveedores y todas las transcripciones. Haga la copia de seguridad con el contenedor detenido, porque copiar una base de datos SQLite mientras se está escribiendo puede producir un archivo que no se pueda abrir. La misma disciplina de detener y después copiar se aplica a todos los contenedores con estado del equipo, aunque los detalles varían según el servicio, ya que PhotoPrism e Immich necesitan sus propios comandos de copia de seguridad.

docker stop harnessrouter
docker run --rm -v harnessrouter:/data -v "$PWD":/backup alpine \
  tar czf /backup/harnessrouter-data.tgz -C / data
docker start harnessrouter

La variante de Compose y la línea que debe cambiar

El repositorio incluye un archivo de Compose. Publica "3000:3000", lo que significa que está disponible en todas las interfaces del host. Cambie esa línea antes de iniciarlo en un servidor público.

services:
  harnessrouter:
    image: harnessrouter/harnessrouter:0.5.5
    ports:
      - "127.0.0.1:3000:3000"
    env_file:
      - .env
    volumes:
      - harnessrouter-data:/data
    restart: unless-stopped

volumes:
  harnessrouter-data:

Hay dos diferencias respecto al repositorio original: la dirección de enlace y una etiqueta de versión fijada en lugar de latest. Fijar la versión es importante porque se publicaron dieciséis etiquetas de versión entre el 9 y el 18 de agosto de 2026, y es difícil depurar un runtime del agente que cambia sin control. Después, copie el archivo de entorno, restrinja sus permisos e inícielo.

cp .env.example .env
chmod 600 .env
docker compose up -d
docker compose logs -f

.env contiene la clave del proveedor en texto plano, por lo que el modo 600 es el mínimo necesario. Si no conoce el subcomando docker compose, la guía rápida de comandos de Docker Compose cubre las operaciones habituales.

Por qué el puerto se publica en 127.0.0.1 y no en 0.0.0.0

-p 3000:3000 publica el puerto en todas las interfaces del host. -p 127.0.0.1:3000:3000 lo publica sólo en loopback, por lo que la única forma de acceder es desde el propio VPS. El contenedor siempre escucha en el puerto 3000 internamente, así que el lado izquierdo es el que debe cambiar. Compruebe el resultado:

docker port harnessrouter
sudo ss -ltnp | grep 3000

La salida de ss que muestra 127.0.0.1:3000 es correcta. 0.0.0.0:3000 significa que la consola está en Internet público. En este caso, eso es más grave que en la mayoría de las aplicaciones autohospedadas, porque la consola crea harnesses, lee todas las transcripciones, ejecuta agentes y proporciona a esos agentes un shell y un sistema de archivos real en su espacio de trabajo. También contiene la clave del proveedor que conectó. Cualquiera que acceda a una consola sin protección puede leer su trabajo, ejecutar comandos y consumir su clave.

Un firewall del host no evita este problema. Docker publica los puertos escribiendo sus propias reglas en la tabla nat del kernel, y estas se evalúan antes de la cadena que administra ufw. Por eso, un puerto publicado sigue siendo accesible aunque sudo ufw status lo muestre como denegado. Pruebe desde otra máquina, no desde el VPS, porque de lo contrario no comprobará nada. Esta es la misma lección que ejecutar dsh sin interfaz en el puerto 3080: vincule el servicio a loopback y decida después, de forma deliberada, cómo acceder a él.

Cambie el inicio de sesión predeterminado antes de hacer cualquier otra cosa

Inicie sesión en http://localhost:3000 con el nombre de usuario harnessrouter y la contraseña harnessrouter. Esas credenciales aparecen en el README porque son valores provisionales, no secretos. El contenedor muestra una advertencia en cada inicio hasta que las cambie:

using the DEFAULT password. Set HR_AUTH_PASSWORD, or change it from the profile page, before exposing this instance.

Cámbiela desde la página Profile o establézcala al iniciar el contenedor para una implementación mediante scripts. HR_AUTH_USER y HR_AUTH_PASSWORD sustituyen los valores predeterminados.

docker run -d --name harnessrouter \
  -p 127.0.0.1:3000:3000 \
  -v harnessrouter:/data \
  -e HR_AUTH_USER='you' \
  -e HR_AUTH_PASSWORD='the-password-you-chose' \
  harnessrouter/harnessrouter

No hay correo de restablecimiento porque no existe un sistema de cuentas ni un servidor de correo. Si pierde la contraseña, elimine el archivo de autenticación del volumen y reinicie. Después, inicie sesión de nuevo con los valores predeterminados.

docker stop harnessrouter
docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json
docker start harnessrouter

HR_AUTH_DISABLED=1 elimina por completo la pantalla de inicio de sesión. El README limita su uso a «un equipo al que nadie más pueda acceder». Un VPS con una dirección IP pública no cumple esa condición. Por tanto, mantenga activada la pantalla de inicio de sesión, salvo que ejecute esto en un portátil.

Compruebe su versión, porque las antiguas no tienen ninguna barrera de autenticación

Esta es la parte que debe tomarse en serio. Las versiones 0.1.x y 0.2.0 se publicaron sin ninguna barrera de autenticación: cualquiera que pudiera acceder al puerto 3000 ya tenía acceso a la consola. 0.3.0 fue la primera versión con inicio de sesión. Esas etiquetas antiguas siguen publicadas y se pueden descargar, por lo que una etiqueta antigua fijada, o un archivo compose copiado de un colega, puede dejar hoy una consola sin autenticación en un puerto público.

A fecha del 19 de agosto de 2026, la etiqueta publicada más reciente es 0.5.5, fechada el 18 de agosto de 2026, y latest apunta a ella. Compruebe qué versión tiene y compárela con la lista de etiquetas de Docker Hub:

docker image ls harnessrouter/harnessrouter

Cualquier versión anterior a 0.3.0 debe sustituirse ahora, no programarse para más adelante. Las versiones iguales o posteriores todavía requieren cambiar la contraseña, porque para alguien que analiza el puerto 3000 una contraseña predeterminada y ninguna contraseña son lo mismo. No considere actuales los números de versión de esta página. Eran correctos en la fecha indicada al principio, y este proyecto publica versiones con rapidez.

Conectar un proveedor

Nada funciona hasta que se conecta un proveedor de modelos. Añada uno desde la página Integrations de la consola o páselo a docker run mediante el entorno. El valor es JSON, así que debe entrecomillarlo en el shell:

-e HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC='{"name":"anthropic","provider":"anthropic","api_key":"sk-ant-…"}'

.env.example define una variable de conexión por familia de proveedores: HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC para el backend claude-code, HR_SECRET_GLOBAL_HARNESS_CONN_OPENAI para el backend codex y HR_SECRET_GLOBAL_HARNESS_CONN_CUSTOM para cualquier endpoint compatible con OpenAI, que es donde se conecta un agregador o su propio servidor de inferencia. Las variables HR_SECRET_GLOBAL_HARNESS_POLICY_CLAUDE, HR_SECRET_GLOBAL_HARNESS_POLICY_CODEX y HR_SECRET_GLOBAL_HARNESS_POLICY_HERMES correspondientes indican qué conexión usa cada backend de forma predeterminada. HR_SECRET_KEY es independiente y sólo es necesario cuando se conecta una base de datos a un agente.

HR_BACKENDS selecciona qué backends se cargan, como en HR_BACKENDS=claude,codex,hermes. Hay un problema conocido que debe tener en cuenta: cualquier valor que omita hermes hace que el contenedor termine inmediatamente con el estado 1 y sin mostrar ningún mensaje de error. Verá Exited (1) en docker ps -a un segundo después de iniciar el contenedor, y docker logs no mostrará nada útil. Mantenga hermes en la lista hasta que el proyecto upstream lo corrija. Si Hermes es el único harness que quiere usar, ejecutar el agente Hermes en su propio VPS es una implementación más pequeña.

Llama a la API sin la consola

La consola es opcional. Ambos usan la misma API, que sigue un contrato de estilo Responses. Inicia sesión primero para obtener una cookie de sesión:

curl -c hr.cookies http://localhost:3000/api/selfhost/login \
  -H 'content-type: application/json' \
  -d '{"username":"harnessrouter","password":"your-password"}'

Después envía una tarea. Especifica el harness en metadata.harness_id y un modelo que el proveedor conectado realmente ofrezca:

curl -s -b hr.cookies http://localhost:3000/api/harness/v1/responses \
  -H 'content-type: application/json' \
  -d '{"input":"Reply with exactly this and nothing else: it works.",
       "metadata":{"harness_id":"codex"},
       "model":"gpt-5.4-mini",
       "stream":false}'

Un objeto JSON que contiene un bloque de salida y un recuento de tokens indica que el harness se ejecutó. Cambiar harness_id de codex a claude envía la misma solicitud a otro harness. Ese cambio es el motivo principal de la existencia de este software. La conexión personalizada anterior permite dirigir un harness a un endpoint compatible con OpenAI que ya alojes, como se configura un harness DeepSeek autoalojado en un VPS.

Acceda desde su portátil sin publicar un puerto

Hay dos formas, y ninguna expone un puerto directamente en 0.0.0.0.

Un túnel SSH es la opción más sencilla y no requiere instalar nada en el servidor. Reenvía un puerto local de su equipo al loopback del VPS.

ssh -N -L 3000:127.0.0.1:3000 you@your-vps

Deje el túnel en ejecución y abra http://localhost:3000 en el navegador. Si SSH muestra bind: Address already in use, otro proceso de su portátil ya usa el puerto 3000. Elija otro puerto local con -L 3100:127.0.0.1:3000 y acceda al puerto 3100.

Un reverse proxy con terminación TLS es la opción adecuada cuando otras personas necesitan acceder. El proxy gestiona el certificado TLS (seguridad de la capa de transporte) y reenvía las peticiones al loopback. El README incluye una configuración de Caddy:

console.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000 {
        flush_interval -1      # agent turns stream for minutes; never buffer them
    }
}

flush_interval -1 es la línea que suele omitirse. Agent transmite tokens durante varios minutos, y un proxy que almacena en búfer la respuesta retiene esos tokens hasta que termina el turno. Por eso la consola parece bloqueada y después muestra todo de una vez. El equivalente en Nginx es proxy_buffering off; dentro del bloque location. Elija la opción que prefiera, pero mantenga el nombre DNS apuntando al proxy y el contenedor en el loopback. Comparación de Nginx, Caddy y Traefik como reverse proxy explica cuál se adapta mejor a su equipo.

Ejecutarlo con un usuario propio, no como root

El daemon de Docker se ejecuta como root, y pertenecer al grupo docker equivale a tener privilegios de root, porque un miembro puede iniciar un contenedor que monte el sistema de archivos del host. Por tanto, «añadir el equipo al grupo docker» otorga acceso root al servidor que contiene la clave del proveedor.

La opción sencilla consiste en crear una cuenta de servicio propietaria del archivo de Compose y de .env, y mantener esos archivos fuera de cualquier directorio de inicio compartido.

sudo adduser --disabled-password --gecos "" harness
sudo install -d -o harness -g harness -m 750 /srv/harnessrouter

La opción más segura es Docker rootless, donde el propio daemon se ejecuta con ese usuario sin privilegios. Necesita el paquete uidmap para newuidmap y newgidmap, y al menos 65536 UID subordinados en /etc/subuid y /etc/subgid para el usuario.

sudo apt install -y uidmap docker-ce-rootless-extras
sudo loginctl enable-linger harness
sudo -iu harness
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
systemctl --user enable --now docker

loginctl enable-linger no es opcional en este caso. Sin él, la instancia de systemd del usuario se detiene cuando se cierra la última sesión, por lo que el contenedor muere al cerrar la sesión. Confirme el resultado con docker info, que muestra rootless en Security Options. El modo rootless no puede enlazar puertos inferiores a 1024 sin configuración adicional. Esto no importa aquí porque el puerto 3000 está por encima de ese límite. La configuración de la cuenta se explica en crear usuarios con privilegios mínimos en un VPS.

Qué falla y qué verá

El contenedor se cierra un segundo después de iniciarse y los registros están vacíos. docker ps -a muestra Exited (1). Ese es el problema de HR_BACKENDS indicado arriba: faltaba hermes en el valor. Vuelva a añadirlo.

El primer inicio nunca termina. El registro se detiene después de una línea de installing y ready on :3000 nunca aparece. El servidor no puede acceder a la red para descargar las CLI del agente porque no están incluidas en la imagen. Corrija la ruta saliente o la configuración del proxy y reinicie.

La consola carga, pero todas las tareas fallan. No hay ningún proveedor conectado. La imagen no incluye un modelo integrado ni un nivel gratuito, por lo que una instancia nueva puede iniciar sesión y aun así no ejecutar nada.

La consola se bloquea a mitad de la respuesta detrás de un proxy. La salida aparece en un solo bloque cuando termina el turno. Se trata de un búfer de respuesta. Configure flush_interval -1 en Caddy o proxy_buffering off; en Nginx.

No puede acceder desde su portátil y el túnel está activo. Ejecute docker port harnessrouter en el servidor. Si no muestra nada, el contenedor no publica ningún puerto porque se inició sin -p.

¿Merece la pena ejecutarlo?

Merece la pena ejecutarlo si realmente usa más de un harness y quiere un único endpoint y un único almacén de credenciales en lugar de tres de cada uno. También merece la pena si está desarrollando un producto sobre esta base y quiere que el harness sea un valor de configuración en lugar de tener que reescribirlo. Eso es lo que proporciona UHP, con la salvedad indicada anteriormente sobre lo reciente que es el protocolo.

No merece la pena ejecutarlo si usa un solo harness. Instalar esa CLI en el servidor implica menos componentes, y no hay ningún inicio de sesión entre usted y ella. Tampoco es la arquitectura adecuada si quiere que varios agentes colaboren en una tarea, en lugar de tener una API delante de varios harnesses. Para ese caso se necesita otra herramienta: consulte un harness multiagente como Omnigent. En cualquier caso, las reglas de despliegue no cambian. Enlace en loopback, contraseña modificada, una etiqueta fijada en 0.3.0 o posterior y un usuario propio.

FAQ

¿Es seguro publicar HarnessRouter en el puerto 3000?

No. La consola crea harnesses, lee todas las transcripciones, ejecuta agentes con acceso al shell y al sistema de archivos, y almacena la clave del proveedor que conectó, por lo que un puerto abierto expone todo eso. Publíquelo en loopback con -p 127.0.0.1:3000:3000 y acceda mediante un túnel SSH o un reverse proxy con terminación TLS. Un firewall del host no basta por sí solo: Docker escribe sus propias reglas en la tabla del kernel nat, por lo que un puerto publicado responde desde Internet aunque ufw lo muestre como denegado. Verifíquelo con sudo ss -ltnp | grep 3000, que debería imprimir 127.0.0.1:3000.

¿Qué versión de HarnessRouter añadió la pantalla de inicio de sesión?

0.3.0. Las versiones 0.1.x y 0.2.0 se publicaron sin autenticación, y ambas etiquetas siguen publicadas y se pueden descargar, por lo que quien las ejecute depende de que nadie encuentre el puerto. Al 19 de agosto de 2026, la etiqueta más reciente es 0.5.5, con fecha del 18 de agosto de 2026. Ejecute docker image ls harnessrouter/harnessrouter para ver qué versión tiene, compárela con la lista de etiquetas de Docker Hub y no con esta página, y cambie la contraseña predeterminada incluso en una versión actual.

¿Por qué el contenedor sale inmediatamente después de configurar HR_BACKENDS?

Cualquier valor de HR_BACKENDS que omita hermes hace que el contenedor salga inmediatamente con el estado 1 y sin mensaje de error. Es un problema conocido que figura en el README del proyecto. El síntoma es Exited (1) en docker ps -a al cabo de uno o dos segundos, sin información útil en docker logs. Mantenga hermes en la lista, como en HR_BACKENDS=claude,codex,hermes, hasta que el proyecto lo corrija.

¿Necesita HarnessRouter acceso a Internet durante el primer arranque?

Sí. Las CLI de los agentes se descargan durante el primer arranque en lugar de incluirse en la imagen, porque cada una tiene su propia licencia. Un equipo sin una ruta de salida muestra las líneas installing y después nunca llega a ready on :3000. La descarga se realiza una vez por volumen, por lo que los arranques posteriores tardan unos segundos y no necesitan red, salvo la necesaria para el proveedor de modelos que conectó.

He perdido la contraseña de la consola. ¿Cómo puedo volver a acceder?

No hay correo de restablecimiento porque no existe un sistema de cuentas ni un servidor de correo. Detenga el contenedor, elimine /data/selfhost-auth.json del volumen, vuelva a iniciarlo, inicie sesión con las credenciales predeterminadas y establezca una contraseña nueva desde la página Profile. Si el contenedor y el volumen se llaman harnessrouter, los comandos son docker stop harnessrouter, después docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json y, por último, docker start harnessrouter.