SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-09-08

Como hospedar o HarnessRouter com uma API única

Veja o deploy Docker do HarnessRouter, o bind em loopback, o login padrão que deve ser alterado e como liberar acesso TLS para Codex, Claude Code e Hermes.

O que o HarnessRouter remove

Você aloja o HarnessRouter Community Edition no seu próprio servidor para colocar uma API à frente de vários agent harnesses. Um agent harness é o programa de linha de comandos que executa um modelo num ciclo: mantém uma sessão, edita ficheiros, executa comandos e transmite o progresso de volta para quem solicitou o trabalho. Codex, Claude Code e Hermes fazem esse trabalho, mas cada um tem o seu próprio processo de instalação, o seu próprio formato de credenciais e a sua própria definição de sessão. O HarnessRouter executa todos no mesmo contentor e disponibiliza um único endpoint HTTP, um único login e um único armazenamento de segredos.

Essa é a ideia principal, mas vale a pena explicitar o custo. Você adiciona um contentor, um login, um volume e um processo de atualização ao seu servidor para transformar vários componentes numa única interface. Se atualmente executa exatamente um harness, esta configuração é pior do que instalar esse harness diretamente. Essa compensação é explicada na última secção, por isso leia-a antes de fazer a implementação.

Tudo abaixo foi verificado com a tag de imagem 0.5.5, obtida em 19 de agosto de 2026. O projeto publica novas tags na maioria dos dias, por isso verifique a tag que realmente executa em vez de confiar nesta página daqui a um mês. Os comandos vêm do README do projeto em github.com/HarnessRouter/harnessrouter.

O que o Unified Harness Protocol realmente é

O HarnessRouter implementa o Unified Harness Protocol (UHP), publicado em unifiedharnessprotocol.org. O UHP descreve como um produto inicia uma tarefa num harness, acompanha a tarefa enquanto ela é executada, gere sessões e ficheiros e comunica falhas. A especificação é versionada por data. A versão em vigor em 19 August 2026 tem a data 2026-08-11, e o site designa-a como um padrão em desenvolvimento, "stable enough to build on, versioned so it can change safely".

Leia com atenção a expressão "open standard". A mesma empresa escreve a especificação, a implementação de referência e o conjunto de testes de conformidade com 52 verificações que determina quem está em conformidade. Isto é normal num protocolo tão recente, e a licença Apache-2.0 permite criar um fork de qualquer parte dele. Também significa que o UHP ainda não é um padrão de vários fornecedores. Trate-o como um protocolo emergente: útil, sujeito a alterações e algo que o seu próprio código deve poder deixar de usar sem exigir uma reescrita.

O que é necessário antes de começar

Docker e aproximadamente 4 GB de espaço livre em disco. Também precisa de uma chave de API de um fornecedor de modelos que já utilize num plano pago. O download da imagem ocupa cerca de 700 MB. O espaço restante é utilizado pelas CLIs dos agentes e pelos workspaces onde estas gravam ficheiros. A imagem não inclui um modelo nem uma chave de avaliação. Por isso, as tarefas falham até ligar um fornecedor. O HarnessRouter utiliza a licença Apache-2.0. As CLIs dos agentes não estão abrangidas por essa licença. Por esse motivo, são transferidas na primeira inicialização em vez de serem incluídas na imagem.

Hospedar o HarnessRouter com um único docker run

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

Em seguida, monitorize o arranque do contentor. O primeiro arranque é lento, e os logs explicam o motivo.

docker logs -f harnessrouter

Durante o arranque, verá linhas como estas:

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

Aguarde por ready on :3000. Essa instalação ocorre uma vez por volume. Por isso, os arranques seguintes demoram alguns segundos e não mostram linhas de instalação.

Esse download tem duas consequências importantes num VPS. Primeiro, o primeiro arranque precisa de acesso à rede de saída. A imagem não é autónoma. Se o servidor estiver atrás de um filtro de saída ou não tiver uma rota para fora, ficará bloqueado neste ponto e nunca mostrará ready on :3000. A falha ocorre no primeiro arranque, e não em docker pull. É um local confuso para descobrir o problema. Segundo, está a instalar software de terceiros sob termos de terceiros. O Claude Code é fornecido sob os termos da Anthropic, e o Hermes está sujeito aos termos definidos pelo projeto upstream. Consulte ambos antes de utilizar o software comercialmente.

-v harnessrouter:/data cria um volume Docker com nome. Todos os dados persistentes ficam em /data: as bases de dados SQLite, os ficheiros armazenados, o armazenamento de segredos e os espaços de trabalho dos agentes. Eliminar esse volume elimina a instância, incluindo as chaves dos fornecedores e todas as transcrições. Faça a cópia de segurança com o contentor parado, porque copiar uma base de dados SQLite enquanto está a ser gravada pode produzir um ficheiro que não abre. A mesma disciplina de parar e copiar aplica-se a todos os contentores com estado no servidor, embora os detalhes variem conforme o serviço, uma vez que PhotoPrism e Immich precisam dos seus próprios comandos de cópia de segurança.

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

A variante do compose e a linha que deve alterar

O repositório inclui um ficheiro compose. Ele publica "3000:3000", o que significa que fica acessível em todas as interfaces do host. Altere essa linha antes de iniciar o serviço num 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:

Há duas diferenças em relação ao upstream: o endereço de bind e uma tag de versão fixa em vez de latest. Fixar a versão é importante porque foram publicadas dezasseis tags de versão entre 9 e 18 August 2026, e é difícil depurar um runtime de agente que muda sem controlo. Em seguida, copie o ficheiro de ambiente, restrinja as respetivas permissões e inicie o serviço.

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

.env contém a chave do seu provider em texto simples, por isso 600 é o mínimo. Se o subcomando docker compose não lhe for familiar, a folha de referência dos comandos do Docker Compose cobre os verbos usados diariamente.

Por que a porta é publicada em 127.0.0.1 e não em 0.0.0.0

-p 3000:3000 publica a porta em todas as interfaces do host. -p 127.0.0.1:3000:3000 publica-a apenas na interface de loopback, o que significa que a única forma de aceder é a partir do próprio VPS. O contentor escuta sempre na porta 3000 internamente, por isso o lado esquerdo é a parte que deve alterar. Verifique o resultado:

docker port harnessrouter
sudo ss -ltnp | grep 3000

ss a mostrar 127.0.0.1:3000 está correto. 0.0.0.0:3000 significa que a consola está acessível através da Internet pública. Neste caso, isso é mais grave do que na maioria das aplicações self-hosted, porque a consola cria harnesses, lê todas as transcrições, executa agentes e fornece a esses agentes uma shell e um sistema de ficheiros real no respetivo workspace. Também contém a chave do provider que ligou. Qualquer pessoa que aceda a uma consola sem proteção pode ler o seu trabalho, executar comandos e gastar a sua chave.

Uma firewall do host não resolve este problema. O Docker publica as portas ao escrever as suas próprias regras na tabela nat do kernel, e essas regras são avaliadas antes da cadeia gerida por ufw. Por isso, uma porta publicada continua acessível mesmo quando sudo ufw status a lista como bloqueada. Teste a partir de outra máquina, não a partir do VPS, caso contrário não estará a testar nada. Esta é a mesma lição de executar dsh headless na porta 3080: associe o serviço à interface de loopback e decida depois, de forma explícita, como irá aceder-lhe.

Altere o login predefinido antes de qualquer outra coisa

Inicie sessão em http://localhost:3000 com o nome de utilizador harnessrouter e a palavra-passe harnessrouter. Essas credenciais estão impressas no README porque são valores provisórios, não segredos, e o contentor avisa-o em cada arranque até que as altere:

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

Altere-as na página Profile ou defina-as no arranque para uma implementação com script. HR_AUTH_USER e HR_AUTH_PASSWORD substituem os valores predefinidos.

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

Não existe email de reposição, porque não existe um sistema de contas nem um servidor de correio. Se perder a palavra-passe, elimine o ficheiro de autenticação no volume e reinicie. Em seguida, inicie sessão novamente com os valores predefinidos.

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

HR_AUTH_DISABLED=1 remove completamente a barreira de início de sessão. O README limita esta opção a "uma máquina à qual mais ninguém consiga aceder". Uma VPS com um endereço IP público não é essa máquina, por isso mantenha a autenticação ativada, exceto se estiver a executar isto num portátil.

Verifique a versão, porque as antigas não têm controlo de acesso

Esta é a parte que deve ser levada a sério. As versões 0.1.x e 0.2.0 foram lançadas sem qualquer controlo de autenticação: qualquer pessoa que conseguisse aceder à porta 3000 já estava dentro da consola. A 0.3.0 foi a primeira versão com início de sessão. Essas tags antigas continuam publicadas e podem continuar a ser obtidas, por isso uma tag antiga fixada, ou um ficheiro compose copiado de um colega, pode disponibilizar hoje uma consola sem controlo de acesso numa porta pública.

Em 19 de agosto de 2026, a tag publicada mais recente é 0.5.5, datada de 18 de agosto de 2026, e latest aponta para ela. Verifique o que tem instalado e compare-o com a lista de tags no Docker Hub:

docker image ls harnessrouter/harnessrouter

Tudo o que estiver abaixo de 0.3.0 deve ser substituído agora, e não agendado. Tudo o que estiver nessa versão ou acima dela continua a exigir a alteração da palavra-passe, porque uma palavra-passe predefinida e a ausência de palavra-passe são a mesma coisa para alguém que esteja a analisar a porta 3000. Não considere atuais os números de versão desta página. Eram válidos na data indicada no início, e este projeto lança versões rapidamente.

Conecte um provider

Nada funciona até que um provider de modelos esteja conectado. Adicione um na página Integrations do console ou passe-o para docker run no ambiente. O valor é JSON, portanto, coloque-o entre aspas no shell:

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

.env.example define uma variável de conexão para cada família de providers: HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC para o backend claude-code, HR_SECRET_GLOBAL_HARNESS_CONN_OPENAI para o backend codex e HR_SECRET_GLOBAL_HARNESS_CONN_CUSTOM para qualquer endpoint compatível com OpenAI, que é onde deve ser configurado um agregador ou o seu próprio servidor de inferência. As variáveis HR_SECRET_GLOBAL_HARNESS_POLICY_CLAUDE, HR_SECRET_GLOBAL_HARNESS_POLICY_CODEX e HR_SECRET_GLOBAL_HARNESS_POLICY_HERMES correspondentes indicam qual conexão cada backend usa por padrão. HR_SECRET_KEY é um item separado e só é necessário quando você conecta um banco de dados a um agente.

HR_BACKENDS seleciona quais backends são carregados, como em HR_BACKENDS=claude,codex,hermes. Há um problema conhecido que você deve saber antes de encontrá-lo: qualquer valor que não inclua hermes faz o container sair imediatamente com status 1 e sem mensagem de erro. Você vê Exited (1) em docker ps -a um segundo depois da inicialização, e docker logs não mostra nada útil. Mantenha hermes na lista até que o upstream corrija o problema. Se o Hermes for o único harness que você quer usar, execute o agente Hermes no próprio VPS para ter uma implantação menor.

Chame a API sem a consola

A consola é opcional. A mesma API serve ambos e usa um contrato no estilo Responses. Inicie sessão primeiro para obter um cookie de sessão:

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

Depois envie uma tarefa, indicando o harness em metadata.harness_id e um modelo que o seu fornecedor ligado disponibilize efetivamente:

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

Um objeto JSON com um bloco de saída e uma contagem de tokens significa que o harness foi executado. Alterar harness_id de codex para claude envia o mesmo pedido para um harness diferente, e essa substituição é a razão de existência deste software. A ligação personalizada acima permite apontar um harness para um endpoint compatível com OpenAI que já aloja, tal como está configurado um harness DeepSeek autoalojado numa VPS.

Aceda a partir do seu portátil sem publicar uma porta

Há duas formas, e nenhuma delas expõe uma porta diretamente em 0.0.0.0.

Um túnel SSH é a opção mais simples e não exige qualquer instalação no servidor. Ele encaminha uma porta local da sua máquina para o loopback na VPS.

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

Mantenha esse comando em execução e abra http://localhost:3000 no navegador. Se o SSH apresentar bind: Address already in use, outro processo no seu portátil já está a usar a porta 3000. Nesse caso, escolha outra porta local com -L 3100:127.0.0.1:3000 e aceda à porta 3100.

Um reverse proxy com terminação TLS é a solução quando outras pessoas precisam de acesso. O proxy gere o certificado TLS (transport layer security) e encaminha as ligações para o loopback. O README fornece uma configuração do 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 é a linha que costuma ser esquecida. O Agent transmite tokens durante vários minutos, e um proxy que armazena a resposta mantém esses tokens retidos até ao fim da interação. Por isso, a consola parece bloqueada e depois apresenta tudo de uma vez. No Nginx, o equivalente é proxy_buffering off; dentro do bloco location. Qualquer que seja a opção escolhida, mantenha o nome DNS apontado para o proxy e o contentor no loopback. Comparar Nginx, Caddy e Traefik como reverse proxy explica qual se adapta melhor ao seu servidor.

Execute-o com um utilizador próprio, não como root

O daemon do Docker é executado como root, e pertencer ao grupo docker equivale a ter privilégios de root, porque um membro pode iniciar um contentor que monte o sistema de ficheiros do host. Por isso, "adicionar a equipa ao grupo docker" concede acesso de root ao servidor que armazena a chave do seu fornecedor.

A versão simples: crie uma conta de serviço que seja proprietária do ficheiro Compose e de .env, e mantenha esses ficheiros fora de qualquer diretório pessoal partilhado.

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

A opção mais segura é o Docker rootless, em que o próprio daemon é executado por esse utilizador sem privilégios. É necessário o pacote uidmap para newuidmap e newgidmap, além de pelo menos 65536 UIDs subordinados em /etc/subuid e /etc/subgid para o utilizador. uidmap está disponível no arquivo do Ubuntu, mas docker-ce-rootless-extras não: é distribuído pelo próprio repositório apt do Docker em download.docker.com, que a instalação do Docker engine adiciona. Se o engine não tiver sido instalado a partir desse repositório, grep -rl download.docker.com /etc/apt/sources.list.d/ não mostra nada e a instalação abaixo não encontrará o pacote.

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 não é opcional neste caso. Sem ele, a instância systemd do utilizador termina quando a última sessão é fechada, e o contentor é encerrado quando termina a sessão. Confirme o resultado com docker info, que lista rootless em Security Options. O modo rootless não pode associar portas abaixo de 1024 sem configuração adicional, mas isso não é relevante aqui porque a porta 3000 está acima desse limite. A configuração da própria conta é descrita em criar utilizadores com privilégios mínimos num VPS.

O que falha e o que verá

O contentor termina um segundo depois de arrancar e os logs estão vazios. docker ps -a mostra Exited (1). Esse é o problema de HR_BACKENDS referido acima: o seu valor não incluía hermes. Volte a adicioná-lo.

O primeiro arranque nunca termina. O log para depois de uma linha installing e ready on :3000 nunca aparece. O servidor não consegue aceder à rede para obter as CLIs dos agentes, porque elas não estão na imagem. Corrija a rota de saída ou as definições do proxy e reinicie.

A consola carrega, mas todas as tarefas falham. Não existe nenhum provider ligado. A imagem não inclui um modelo integrado nem um nível gratuito, por isso uma instância nova pode permitir iniciar sessão, mas não executa nada.

A consola bloqueia a meio da resposta atrás de um proxy. A saída aparece num único bloco quando a interação termina. Isso é buffering da resposta. Defina flush_interval -1 no Caddy ou proxy_buffering off; no Nginx.

Não consegue aceder-lhe a partir do portátil e o túnel está ativo. Execute docker port harnessrouter no servidor. Se não mostrar nada, o contentor não publica nenhuma porta, porque foi iniciado sem -p.

Vale a pena executar isto?

Vale a pena executá-lo se utilizar realmente mais de um harness e quiser um endpoint e um armazenamento de credenciais, em vez de três de cada. Também vale a pena se estiver a criar um produto sobre esta camada e quiser que o harness seja um valor de configuração, em vez de exigir uma reescrita. É isso que o UHP oferece, com a ressalva acima sobre a pouca maturidade do protocolo.

Não vale a pena executá-lo se utilizar apenas um harness. Instalar essa CLI no servidor envolve menos componentes, e não há um login entre si e a ferramenta. Também não é a opção adequada se pretende vários agentes a colaborar numa única tarefa, em vez de uma API à frente de vários harnesses. Esse é um caso de utilização para uma ferramenta diferente: consulte um harness multiagente como o Omnigent. Em qualquer caso, as regras de implementação não mudam. Faça o bind em loopback, altere a palavra-passe, use uma tag fixada em 0.3.0 ou posterior e execute o serviço com o seu próprio utilizador.

FAQ

É seguro publicar o HarnessRouter na porta 3000?

Não. A consola cria harnesses, lê todas as transcrições, executa agentes com acesso à shell e ao sistema de ficheiros e mantém a chave do fornecedor que ligou. Por isso, uma porta aberta expõe tudo isso. Publique-o em loopback com -p 127.0.0.1:3000:3000 e aceda através de um túnel SSH ou de um reverse proxy com terminação TLS. Uma firewall no host não é suficiente por si só: o Docker escreve as suas próprias regras na tabela nat do kernel, por isso uma porta publicada responde a partir da Internet mesmo quando ufw a mostra como bloqueada. Verifique com sudo ss -ltnp | grep 3000. O comando deve apresentar 127.0.0.1:3000.

Que versão do HarnessRouter adicionou o controlo de início de sessão?

0.3.0. As versões 0.1.x e 0.2.0 foram lançadas sem qualquer autenticação. Ambas as tags continuam publicadas e disponíveis para pull. Quem as executa depende, na prática, de ninguém encontrar a porta. Em 19 August 2026, a tag mais recente é 0.5.5, datada de 18 August 2026. Execute docker image ls harnessrouter/harnessrouter para verificar o que está instalado. Compare esse resultado com a lista de tags no Docker Hub, e não com esta página. Altere também a palavra-passe predefinida numa versão atual.

Por que motivo o contentor termina imediatamente depois de eu definir HR_BACKENDS?

Qualquer valor de HR_BACKENDS que não inclua hermes faz o contentor terminar imediatamente com o estado 1 e sem mensagem de erro. Este é um problema conhecido no README do projeto. O sintoma é Exited (1) em docker ps -a ao fim de um ou dois segundos, sem informação útil em docker logs. Mantenha hermes na lista, como em HR_BACKENDS=claude,codex,hermes, até o projeto corrigir o problema.

O HarnessRouter precisa de acesso à Internet no primeiro arranque?

Sim. As CLIs dos agentes são descarregadas no primeiro arranque, em vez de serem incluídas na imagem, porque cada uma tem a sua própria licença. Um sistema sem rota de saída apresenta as linhas installing e nunca chega a ready on :3000. O download ocorre uma vez por volume. Os arranques seguintes demoram alguns segundos e não precisam de rede além do fornecedor de modelos que ligou.

Perdi a palavra-passe da consola. Como volto a iniciar sessão?

Não existe email de reposição, porque não há um sistema de contas nem um servidor de email. Pare o contentor, elimine /data/selfhost-auth.json do volume, inicie-o novamente, entre com as credenciais predefinidas e defina uma nova palavra-passe na página Profile. Com o contentor e o volume chamados harnessrouter, execute docker stop harnessrouter, depois docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json e, por fim, docker start harnessrouter.