Hospede o HarnessRouter: uma API para agentes
Execute Codex, Claude Code e Hermes em uma API própria. Veja o deploy Docker, o bind loopback, o login padrão a alterar e o acesso TLS.
O que o HarnessRouter elimina
Você aloja o HarnessRouter Community Edition no seu próprio servidor para colocar uma API à frente de vários harnesses de agentes. Um harness de agente é o programa de linha de comandos que conduz um modelo num ciclo: mantém uma sessão, edita ficheiros, executa comandos e transmite o progresso para quem solicitou o trabalho. Codex, Claude Code e Hermes desempenham essa função. Cada um tem o seu próprio processo de instalação, formato de credenciais e conceito de sessão. O HarnessRouter executa todos dentro de um único contentor e coloca à frente um único endpoint HTTP, um único login e um único armazenamento de segredos.
Essa é a ideia completa. O custo deve ser explicitado. Você adiciona um contentor, um login, um volume e um processo de atualização ao seu servidor para transformar vários componentes em um só. Se atualmente executar exatamente um harness, esta configuração é pior do que instalar esse harness diretamente. Essa decisão é analisada na última secção. Leia-a antes de fazer o deployment.
Tudo abaixo foi verificado com a tag de imagem 0.5.5, obtida em 19 August 2026. O projeto publica novas tags na maioria dos dias. Verifique a tag que você 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 esta é executada, gere sessões e ficheiros e comunica falhas. A especificação é versionada por data. A versão em vigor em 19 August 2026 é datada de 2026-08-11, e o site chama-lhe um padrão em desenvolvimento, "stable enough to build on, versioned so it can change safely".
Leia atentamente a expressão "open standard" neste contexto. 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 significa que pode criar um fork de qualquer parte dele. Também significa que o UHP ainda não é um padrão multi-vendor. Trate-o como um protocolo emergente: útil, em evolução e algo que o seu próprio código deve conseguir deixar de usar sem exigir uma reescrita.
O que precisa 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á utiliza mediante pagamento. A transferência da imagem ocupa cerca de 700 MB. O restante do espaço em disco é utilizado pelas CLIs dos agentes e pelos workspaces onde escrevem. A imagem não inclui nenhum modelo nem chave de avaliação. Por isso, as tarefas falham até ligar um fornecedor. O próprio HarnessRouter está licenciado sob Apache-2.0. As CLIs dos agentes não estão abrangidas por essa licença. Por esse motivo, são transferidas no primeiro arranque 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/harnessrouterEm seguida, acompanhe o arranque do contentor. O primeiro arranque é lento, e os logs mostram o motivo.
docker logs -f harnessrouterDurante o processo, 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, cada arranque posterior demora alguns segundos e não apresenta linhas de instalação.
Esse download implica dois factos importantes num VPS. Primeiro, o primeiro arranque precisa de acesso à rede de saída. A imagem não é autónoma. Por isso, um servidor atrás de um filtro de egress ou sem uma rota de saída fica bloqueado neste ponto e nunca apresenta ready on :3000. A falha ocorre no primeiro arranque, e não em docker pull. Descobrir isso apenas nesse momento é confuso. Segundo, está a instalar software de terceiros sob termos de terceiros. O Claude Code é fornecido ao abrigo dos termos da Anthropic, e o Hermes ao abrigo dos termos definidos pelo respetivo projeto upstream. Consulte ambos antes de os utilizar comercialmente.
-v harnessrouter:/data cria um volume Docker nomeado. Todos os dados persistentes ficam em /data: as bases de dados SQLite, os ficheiros armazenados, o armazenamento de segredos e os workspaces dos agentes. Se eliminar esse volume, elimina a instância, incluindo as chaves dos providers e todas as transcrições. Faça o backup com o contentor parado. Copiar uma base de dados SQLite enquanto está a ser gravada produz um ficheiro que pode não abrir. A mesma disciplina de parar e copiar aplica-se a todos os contentores com estado no servidor. Os detalhes variam consoante o serviço, porque PhotoPrism e Immich precisam dos seus próprios comandos de backup.
docker stop harnessrouter
docker run --rm -v harnessrouter:/data -v "$PWD":/backup alpine \
tar czf /backup/harnessrouter-data.tgz -C / data
docker start harnessrouterA variante compose e a linha que deve alterar
O repositório inclui um ficheiro compose. Ele publica "3000:3000", o que significa todas as interfaces no host. Altere essa linha antes de o iniciar 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 lançadas dezasseis tags de versão entre 9 e 18 August 2026, e é difícil depurar um runtime de agente que muda sem controlo. Depois, 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 o modo 600 é o mínimo. Se o subcomando docker compose não lhe for familiar, a folha de consulta dos comandos Docker Compose cobre os comandos usados no dia a dia.
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 3000ss a apresentar 127.0.0.1:3000 está correto. 0.0.0.0:3000 significa que o console está acessível pela Internet pública. Neste caso, isso é mais grave do que na maioria das aplicações self-hosted, porque o console cria harnesses, lê todas as transcrições, executa agentes e dá a esses agentes acesso a uma shell e a um sistema de ficheiros real no respetivo workspace. Também contém a chave do provider que ligou. Qualquer pessoa que aceda a um console sem proteção pode ler o seu trabalho, executar comandos e gastar a sua chave.
Uma firewall do host não o protege deste problema. O Docker publica portas ao escrever as suas próprias regras na tabela nat do kernel, que 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 sem interface gráfica na porta 3080: associe o serviço à interface de loopback e depois decida deliberadamente como lhe vai aceder.
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 no README porque são valores de substituição, 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 automatizada. 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/harnessrouterNão existe email de reposição, porque não existe um sistema de contas nem um servidor de email. Se perder a palavra-passe, elimine o ficheiro de autenticação no volume e reinicie. Depois, 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 harnessrouterHR_AUTH_DISABLED=1 remove completamente a proteção de login. O README limita o uso desta opção a "uma máquina à qual mais ninguém consiga aceder". Um VPS com um endereço IP público não é essa máquina. Por isso, mantenha a proteção ativa, 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 levar 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, pelo que uma tag antiga fixada, ou um ficheiro compose copiado de um colega, pode expor hoje uma consola sem controlo de acesso numa porta pública.
Em 19 August 2026, a tag publicada mais recente é 0.5.5, datada de 18 August 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/harnessrouterTudo abaixo de 0.3.0 deve ser substituído agora, não agendado. Tudo nessa versão ou acima ainda precisa de ter a palavra-passe alterada, porque, para alguém que esteja a analisar a porta 3000, uma palavra-passe predefinida e a ausência de palavra-passe são a mesma coisa. Não trate os números de versão desta página como atuais. Eram válidos na data indicada no início, e este projeto lança versões rapidamente.
Conectar um provedor
Nada funciona até que um provedor de modelos seja 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 por família de provedores: 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 entra 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 definem qual conexão cada backend usa por padrão. HR_SECRET_KEY é independente e só é necessária 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 o status 1, 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, executar o agente Hermes no próprio VPS é a implantação mais simples.
Chamar a API sem a consola
A consola é opcional. A mesma API serve ambos, e utiliza um contrato ao 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"}'Em seguida, 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 que contenha um bloco de saída e uma contagem de tokens indica que o harness foi executado. Alterar harness_id de codex para claude envia o mesmo pedido para um harness diferente, e essa troca é precisamente a razão de existir 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 requer 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-vpsMantenha esse processo em execução e abra http://localhost:3000 no navegador. Se o SSH mostrar bind: Address already in use, outro processo no seu portátil já está a utilizar 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 opção adequada quando outras pessoas precisam de acesso. O proxy gere o certificado TLS (transport layer security) e encaminha os pedidos 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 muitas pessoas esquecem. O Agent gera tokens de fluxo durante vários minutos, e um proxy que coloca a resposta em buffer retém esses tokens até ao fim da interação. Por isso, a consola parece bloqueada e depois apresenta tudo de uma vez. O equivalente no Nginx é 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 adequa 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 root, porque um membro pode iniciar um contentor que monte o sistema de ficheiros do host. Por isso, “adicionar a equipa ao grupo docker” atribui acesso root ao sistema que guarda a sua chave do 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/harnessrouterA opção mais segura é usar 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.
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 dockerloginctl 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 apresenta rootless em Security Options. O modo rootless não pode associar portas abaixo de 1024 sem configuração adicional. Isso não é relevante neste caso, porque a porta 3000 está acima desse limite. A configuração da própria conta é abordada em criar utilizadores com privilégios mínimos numa 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 sistema não consegue aceder à rede para obter as CLIs do agente, 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 executará 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 é causado pelo buffering da resposta. Defina flush_interval -1 no Caddy ou proxy_buffering off; no Nginx.
Não consegue aceder ao serviço 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?
Vale a pena executar se realmente utiliza mais de um harness e quer um endpoint e um armazenamento de credenciais, em vez de três de cada. Também vale a pena se está a criar um produto sobre esta base e quer que o harness seja um valor de configuração, e não uma reescrita. É isso que o UHP oferece, com a ressalva anterior sobre a pouca maturidade do protocolo.
Não vale a pena executar se utiliza um único 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 tarefa, em vez de uma API à frente de vários harnesses. Esse é um caso de uso para outra ferramenta: consulte um harness multiagente como o Omnigent. Em qualquer dos casos, as regras de implementação não mudam: ligação ao loopback, palavra-passe alterada, uma tag fixada em 0.3.0 ou superior e um utilizador próprio.
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 armazena 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-lhe através de um túnel SSH ou de um reverse proxy com terminação TLS. Uma firewall do host não é suficiente por si só: o Docker escreve as suas próprias regras na tabela do kernel nat, pelo que uma porta publicada responde a partir da Internet mesmo quando ufw a mostra como bloqueada. Verifique com sudo ss -ltnp | grep 3000, que deverá imprimir 127.0.0.1:3000.
Que versão do HarnessRouter adicionou o controlo de autenticação?
0.3.0. As versões 0.1.x e 0.2.0 foram lançadas sem qualquer autenticação, e ambas as tags continuam publicadas e disponíveis para download. Quem as executa está, portanto, a depender 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 ver o que tem instalado, compare o resultado com a lista de tags no Docker Hub, e não com esta página, e altere a palavra-passe predefinida mesmo 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 transferidas 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 depois nunca chega a ready on :3000. A transferência ocorre uma vez por volume. Os arranques seguintes demoram alguns segundos e não precisam de rede para além do fornecedor de modelos que ligou.
Perdi a palavra-passe da consola. Como recupero o acesso?
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, autentique-se com as credenciais predefinidas e defina uma nova palavra-passe na página Profile. Com o contentor e o volume ambos denominados 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.