SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-21

HarnessRouter zelf hosten: één API voor alle agents

Host Codex, Claude Code en Hermes achter één API met HarnessRouter. U leest hier de Docker-configuratie, de loopback-instellingen, het standaardwachtwoord en TLS-toegang.

Wat HarnessRouter wegneemt

U host HarnessRouter Community Edition zelf om één API voor meerdere agent-harnesses op een server in eigen beheer te plaatsen. Een agent-harness is het command-line-programma dat een model in een lus aanstuurt: het onderhoudt een sessie, bewerkt bestanden, voert commando's uit en streamt de voortgang terug naar de aanvrager van het werk. Codex, Claude Code en Hermes voeren elk deze taak uit, en elk wordt geleverd met een eigen installatie, een eigen formaat voor inloggegevens en een eigen definitie van wat een sessie is. HarnessRouter voert ze allemaal uit binnen één container en plaatst er één HTTP-endpoint, één login en één centrale opslag voor geheimen voor.

Dat is het volledige concept, en de kosten zijn het benoemen waard. U voegt een container, een login, een volume en een upgradepad toe aan uw server zodat meerdere bewegende delen één geheel worden. Als u vandaag precies één harness gebruikt, is dit een minder efficiënte opzet dan het direct installeren van die harness. Die afweging staat in het laatste gedeelte hier, dus lees dat voordat u tot implementatie overgaat.

Alles hieronder is gecontroleerd aan de hand van image tag 0.5.5, opgehaald op 19 augustus 2026. Het project publiceert bijna dagelijks nieuwe tags, dus controleer de tag die u daadwerkelijk gebruikt in plaats van deze pagina over een maand nog te vertrouwen. De commando's zijn afkomstig uit de README van het project op github.com/HarnessRouter/harnessrouter.

Wat het Unified Harness Protocol daadwerkelijk is

HarnessRouter implementeert het Unified Harness Protocol (UHP), gepubliceerd op unifiedharnessprotocol.org. UHP beschrijft hoe een product een taak start op een harness, de taak volgt tijdens de uitvoering, sessies en bestanden beheert en fouten rapporteert. De specificatie is voorzien van een datumversie. De versie die op 19 augustus 2026 actief is, draagt de datum 2026-08-11 en de website noemt het een conceptstandaard: "stabiel genoeg om op te bouwen, versiebeheer zorgt voor veilige wijzigingen".

Lees de term "open standaard" hier zorgvuldig. Hetzelfde bedrijf schrijft de specificatie, de referentie-implementatie en de 52-check conformiteitssuite die bepaalt wie voldoet aan de standaard. Dat is gebruikelijk voor een protocol van deze leeftijd, en de Apache-2.0 licentie betekent dat u elk onderdeel ervan kunt forken. Het betekent ook dat UHP nog geen multi-vendor standaard is. Beschouw het als een opkomend protocol: nuttig, in beweging en iets waar uw eigen code zonder herschrijven vanaf moet kunnen stappen.

Vereisten voor aanvang

Docker en ongeveer 4 GB vrije schijfruimte. U heeft tevens een API-key nodig van een modelprovider waarvoor u reeds betaalt. De image-pull is ongeveer 700 MB; de resterende schijfruimte wordt gebruikt door de agent-CLI's en de workspaces waarin deze schrijven. Er is geen model meegeleverd en de image bevat geen proef-key, waardoor taken mislukken totdat u een provider koppelt. HarnessRouter zelf valt onder de Apache-2.0 licentie. De agent-CLI's vallen niet onder deze licentie; daarom worden deze bij de eerste start opgehaald in plaats van meegeleverd in de image.

HarnessRouter zelf hosten met één docker run

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

Observeer vervolgens hoe de container opstart. De eerste keer opstarten duurt lang; de logs geven aan waarom.

docker logs -f harnessrouter

U ziet regels zoals deze terwijl het proces bezig is:

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

Wacht op ready on :3000. Deze installatie vindt eenmalig per volume plaats, waardoor elke volgende start slechts enkele seconden duurt en er geen installatieregels meer worden getoond.

Uit deze download volgen twee feiten die beide van belang zijn op een VPS. Ten eerste: de eerste keer opstarten vereist uitgaande netwerktoegang. De image is niet volledig zelfstandig; een server achter een egress-filter of een server zonder route naar buiten loopt hier vast en zal nooit ready on :3000 tonen. Het proces faalt bij de eerste start, niet bij docker pull, wat een verwarrende plek is om dit te ontdekken. Ten tweede: u installeert software van derden onder de voorwaarden van die derden. Claude Code valt onder de voorwaarden van Anthropic en Hermes onder de voorwaarden van de betreffende upstream-partij; controleer beide voordat u dit commercieel inzet.

-v harnessrouter:/data maakt een named Docker volume aan. Alles wat persistent moet zijn, bevindt zich in /data: de SQLite-databases, de opgeslagen bestanden, de secret store en de agent-workspaces. Als u dat volume verwijdert, verwijdert u de instantie, inclusief de provider-keys en alle transcripten. Maak een back-up terwijl de container is gestopt, omdat het kopiëren van een SQLite-database terwijl er naar geschreven wordt, kan resulteren in een bestand dat niet geopend kan worden. Dezelfde discipline van stoppen-en-dan-kopiëren geldt voor elke stateful container op de server, hoewel de details per service verschillen, aangezien PhotoPrism en Immich elk hun eigen back-upcommando's vereisen.

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

De compose-variant en de regel die u moet wijzigen

De repository bevat een compose-bestand. Dit publiceert "3000:3000", wat betekent dat het op elke interface van de host luistert. Wijzig deze regel voordat u het op een publieke server opstart.

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:

Twee zaken wijken af van de upstream-versie: het bind-adres en een vastgezette versie-tag in plaats van latest. Het vastzetten van versies is belangrijk omdat er tussen 9 en 18 augustus 2026 zestien versie-tags zijn uitgebracht; een agent-runtime die onverwacht wijzigt, is lastig te debuggen. Kopieer vervolgens het omgevingsbestand, beveilig de permissies en start de service.

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

.env bevat uw provider-sleutel in platte tekst, dus mode 600 is het minimum. Als het docker compose-subcommando onbekend is, biedt de spiekbrief voor Docker Compose-commando's een overzicht van de dagelijkse acties.

Waarom de poort op 127.0.0.1 wordt gepubliceerd en niet op 0.0.0.0

-p 3000:3000 publiceert de poort op elke interface die de host heeft. -p 127.0.0.1:3000:3000 publiceert deze alleen op de loopback-interface, wat betekent dat de enige toegangsweg vanaf de VPS zelf is. De container luistert intern altijd op poort 3000, dus de linkerzijde is het deel dat u aanpast. Controleer uw huidige configuratie:

docker port harnessrouter
sudo ss -ltnp | grep 3000

ss waarbij 127.0.0.1:3000 wordt afgedrukt, is correct. 0.0.0.0:3000 betekent dat de console toegankelijk is vanaf het publieke internet. Dat is in dit geval riskanter dan bij de meeste zelfgehoste applicaties, omdat de console harnesses aanmaakt, elk transcript leest, agents uitvoert en deze agents een shell en een echt bestandssysteem in hun workspace geeft. Bovendien bevat de console de provider-key die u heeft gekoppeld. Iedereen die een onbeveiligde console bereikt, kan uw werk inzien, commando's uitvoeren en uw key verbruiken.

Een host-firewall beschermt u hier niet tegen. Docker publiceert poorten door eigen regels naar de kernel nat-tabel te schrijven, en deze worden geëvalueerd voordat de chain ufw deze beheert. Een gepubliceerde poort blijft daarom bereikbaar, zelfs wanneer sudo ufw status deze als geweigerd markeert. Test vanaf een andere machine, niet vanaf de VPS zelf, anders test u niets. Dit is dezelfde les als bij het headless draaien van dsh op poort 3080: bind de service aan de loopback-interface en bepaal vervolgens bewust hoe u deze benadert.

Wijzig de standaard login voordat u iets anders doet

Meld u aan bij http://localhost:3000 met de gebruikersnaam harnessrouter en het wachtwoord harnessrouter. Deze inloggegevens staan in de README omdat het tijdelijke aanduidingen zijn, geen geheimen. De container waarschuwt u bij elke start totdat u ze wijzigt:

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

Wijzig deze via de pagina Profile, of stel ze in bij de start voor een geautomatiseerde implementatie. HR_AUTH_USER en HR_AUTH_PASSWORD overschrijven de standaardwaarden.

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

Er is geen e-mailfunctie voor wachtwoordherstel, omdat er geen accountsysteem en geen mailserver aanwezig is. Als u het wachtwoord verliest, verwijder dan het auth-bestand in de volume-map en start de container opnieuw. Meld u daarna weer aan met de standaardgegevens.

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

HR_AUTH_DISABLED=1 verwijdert de inlogbeveiliging volledig. De README beperkt dit gebruik tot "een systeem dat voor niemand anders bereikbaar is". Een VPS met een publiek IP-adres is niet zo'n systeem, dus laat de beveiliging ingeschakeld tenzij u dit op een laptop draait.

Controleer uw versie, want oudere versies hebben geen toegangsbeveiliging

Dit is een cruciaal punt. Versies 0.1.x en 0.2.0 werden uitgebracht zonder enige vorm van authenticatie: iedereen die poort 3000 kon bereiken, had direct toegang tot de console. 0.3.0 was de eerste release met een inlogscherm. Die oudere tags zijn nog steeds gepubliceerd en kunnen nog steeds worden opgehaald. Hierdoor kan een oude, vastgezette tag of een compose-bestand dat van een collega is gekopieerd, er vandaag de dag voor zorgen dat een onbeveiligde console op een publieke poort beschikbaar is.

Op 19 augustus 2026 is de laatst gepubliceerde tag 0.5.5, gedateerd op 18 augustus 2026, en latest verwijst hiernaar. Controleer welke versie u heeft en vergelijk deze met de lijst met tags op Docker Hub:

docker image ls harnessrouter/harnessrouter

Alles onder 0.3.0 moet direct worden vervangen; plan dit niet voor later. Bij alles op of boven dit versienummer moet het wachtwoord alsnog worden gewijzigd, omdat een standaardwachtwoord voor iemand die poort 3000 scant hetzelfde is als helemaal geen wachtwoord. Beschouw de versienummers op deze pagina niet als actueel. Ze waren correct op de datum bovenaan deze pagina, en dit project brengt snel updates uit.

Een provider koppelen

Niets werkt totdat er een modelprovider is gekoppeld. Voeg er een toe via de pagina Integrations in de console, of geef deze door aan docker run in de omgeving. De waarde is JSON, dus plaats deze tussen aanhalingstekens in de shell:

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

.env.example benoemt één verbindingsvariabele per providerfamilie: HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC voor de claude-code backend, HR_SECRET_GLOBAL_HARNESS_CONN_OPENAI voor de codex backend, en HR_SECRET_GLOBAL_HARNESS_CONN_CUSTOM voor elk OpenAI-compatibel eindpunt; dit is waar een aggregator of uw eigen inference-server wordt geplaatst. De bijbehorende variabelen HR_SECRET_GLOBAL_HARNESS_POLICY_CLAUDE, HR_SECRET_GLOBAL_HARNESS_POLICY_CODEX en HR_SECRET_GLOBAL_HARNESS_POLICY_HERMES bepalen welke verbinding elke backend standaard gebruikt. HR_SECRET_KEY is een afzonderlijk onderdeel en is alleen vereist wanneer u een database aan een agent koppelt.

HR_BACKENDS selecteert welke backends worden geladen, zoals in HR_BACKENDS=claude,codex,hermes. Er is één bekend probleem waar u rekening mee moet houden voordat u er hinder van ondervindt: elke waarde die hermes weglaat, zorgt ervoor dat de container onmiddellijk afsluit met status 1 zonder foutmelding. U ziet Exited (1) in docker ps -a een seconde na het opstarten, en docker logs toont niets nuttigs. Houd hermes in de lijst totdat upstream dit heeft opgelost. Als Hermes de enige harness is die u wilt gebruiken, is de Hermes-agent op een eigen VPS draaien de compactere implementatie.

De API aanroepen zonder de console

De console is optioneel. Dezelfde API bedient beide en hanteert een contract in Responses-stijl. Meld u eerst aan om een sessie-cookie te verkrijgen:

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

Verstuur vervolgens een taak, waarbij u de harness benoemt in metadata.harness_id en een model kiest dat uw verbonden provider daadwerkelijk aanbiedt:

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

Een JSON-object met een output-blok en een token-aantal betekent dat de harness is uitgevoerd. Door harness_id te wijzigen van codex naar claude, wordt hetzelfde verzoek naar een andere harness gestuurd; deze wissel is de volledige reden voor het bestaan van deze software. De bovenstaande aangepaste verbinding is de manier waarop u een harness koppelt aan een OpenAI-compatibel eindpunt dat u reeds host, op de wijze zoals een zelfgehoste DeepSeek-harness op een VPS is geconfigureerd.

Toegang vanaf uw laptop zonder een poort te publiceren

Er zijn twee manieren, en geen van beide vereist een open poort op 0.0.0.0.

Een SSH-tunnel is de meest eenvoudige methode en vereist geen extra installaties op de server. Hiermee wordt een lokale poort op uw machine doorgestuurd naar de loopback-interface van de VPS.

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

Laat dit proces draaien en open http://localhost:3000 in uw browser. Als SSH de melding bind: Address already in use geeft, is poort 3000 op uw laptop al in gebruik. Kies in dat geval een andere lokale poort met -L 3100:127.0.0.1:3000 en navigeer naar poort 3100.

Een terminating reverse proxy is de oplossing wanneer anderen toegang nodig hebben. De proxy beheert het TLS-certificaat (transport layer security) en stuurt het verkeer door naar de loopback-interface. De README bevat een configuratie voor 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 is de regel die vaak wordt vergeten. De agent genereert stream-tokens gedurende enkele minuten; een proxy die de respons buffert, houdt deze tokens vast totdat de beurt is voltooid. Hierdoor lijkt de console bevroren en wordt vervolgens alle output tegelijk getoond. Het equivalent in Nginx is proxy_buffering off; binnen het location-blok. Welke optie u ook kiest, zorg dat de DNS-naam naar de proxy wijst en de container op loopback draait. Vergelijking van Nginx, Caddy en Traefik als reverse proxy helpt u bij het bepalen welke oplossing het beste bij uw server past.

Voer het uit als een eigen gebruiker, niet als root

De Docker daemon draait als root en het lidmaatschap van de docker groep staat gelijk aan root-toegang, omdat een lid een container kan starten die het bestandssysteem van de host mount. Het toevoegen van het team aan de docker-groep geeft dus root-rechten op de machine die uw provider-sleutel bevat.

De eenvoudige versie: maak een service-account aan dat eigenaar is van het compose-bestand en .env, en houd deze bestanden buiten gedeelde home-directories.

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

De robuustere versie is rootless Docker, waarbij de daemon zelf draait als een gebruiker zonder privileges. Hiervoor is het uidmap pakket nodig voor newuidmap en newgidmap, evenals ten minste 65536 ondergeschikte UIDs in /etc/subuid en /etc/subgid voor de gebruiker.

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 is hierbij niet optioneel. Zonder deze instelling stopt de systemd-instantie van de gebruiker zodra de laatste sessie wordt gesloten, waardoor de container stopt wanneer u uitlogt. Bevestig het resultaat met docker info, dat rootless onder Security Options vermeldt. De rootless-modus kan zonder extra configuratie geen poorten onder 1024 binden; dit is hier niet van belang omdat poort 3000 boven die grens ligt. Het opzetten van het account zelf wordt behandeld in het aanmaken van gebruikers met minimale privileges op een VPS.

Wat er misgaat en wat u zult zien

De container stopt een seconde na het starten en de logs zijn leeg. docker ps -a toont Exited (1). Dat is het HR_BACKENDS-probleem van hierboven: uw waarde mist hermes. Voeg deze weer toe.

De eerste start wordt nooit voltooid. De log stopt na een installing-regel en ready on :3000 verschijnt nooit. De server kan het netwerk niet bereiken om de agent-CLI's op te halen, omdat deze niet in de image aanwezig zijn. Herstel de uitgaande route of de proxy-instellingen en start vervolgens opnieuw.

De console laadt, maar elke taak mislukt. Er is geen provider verbonden. Er is geen gebundeld model en geen gratis tier in de image aanwezig, waardoor een nieuwe instantie u wel kan laten inloggen, maar vervolgens niets kan uitvoeren.

De console bevriest tijdens het antwoorden achter een proxy. De output verschijnt in één blok zodra de beurt is beëindigd. Dit is response buffering. Stel flush_interval -1 in voor Caddy, of proxy_buffering off; voor Nginx.

U kunt de server niet bereiken vanaf uw laptop, terwijl de tunnel actief is. Voer docker port harnessrouter uit op de server. Als dit niets weergeeft, publiceert de container niets; hij is dan gestart zonder -p.

Is het de moeite waard om dit te draaien?

Het is de moeite waard om dit te draaien als u daadwerkelijk meer dan één harness gebruikt en u de voorkeur geeft aan één eindpunt en één opslagplaats voor inloggegevens in plaats van drie van elk. Het is ook de moeite waard als u een product bouwt op basis hiervan en wilt dat de harness een configuratiewaarde is in plaats van een herschreven implementatie. Dat is wat UHP u oplevert, met de bovenstaande kanttekening over hoe jong het protocol nog is.

Het is niet de moeite waard om dit te draaien als u slechts één harness gebruikt. Het installeren van die CLI op de server betekent minder bewegende onderdelen en er staat geen inlogprocedure tussen u en de tool in. Het is ook de verkeerde aanpak als u wilt dat verschillende agents samenwerken aan één taak in plaats van één API voor meerdere harnesses; dat is een ander type tool: zie een multi-agent harness zoals Omnigent voor dat patroon. Hoe dan ook, de implementatieregels veranderen niet. Gebruik een loopback-bind, een gewijzigd wachtwoord, een vastgezette tag op 0.3.0 of hoger, en een eigen gebruiker.

FAQ

Is het veilig om HarnessRouter op poort 3000 te publiceren?

Nee. De console maakt harnesses aan, leest elk transcript, voert agents uit met shell- en bestandssysteemtoegang en bevat de provider-key die u heeft gekoppeld. Een open poort stelt dit alles bloot. Publiceer op loopback met -p 127.0.0.1:3000:3000 en bereik de service via een SSH-tunnel of een reverse proxy met TLS-termination. Een host-firewall is op zichzelf niet voldoende: Docker schrijft zijn eigen regels naar de nat-tabel van de kernel, waardoor een gepubliceerde poort reageert vanaf het internet, zelfs wanneer ufw aangeeft dat deze is geweigerd. Controleer dit met sudo ss -ltnp | grep 3000, wat 127.0.0.1:3000 zou moeten weergeven.

In welke HarnessRouter-versie is de inlogbeveiliging toegevoegd?

0.3.0. Versies 0.1.x en 0.2.0 werden uitgebracht zonder enige authenticatie. Beide tags zijn nog steeds gepubliceerd en kunnen worden opgehaald, dus iedereen die deze versies draait, vertrouwt erop dat niemand de poort vindt. Sinds 19 augustus 2026 is de nieuwste tag 0.5.5, gedateerd op 18 augustus 2026. Voer docker image ls harnessrouter/harnessrouter uit om te zien welke versie u heeft, vergelijk dit met de taglijst op Docker Hub in plaats van met deze pagina, en wijzig het standaardwachtwoord, zelfs in een huidige versie.

Waarom stopt de container direct nadat ik HR_BACKENDS heb ingesteld?

Elke HR_BACKENDS-waarde die hermes weglaat, zorgt ervoor dat de container onmiddellijk stopt met status 1 zonder foutmelding. Dit is een bekend probleem in de README van het project. Het symptoom is Exited (1) in docker ps -a binnen een seconde of twee, en niets nuttigs in docker logs. Houd hermes in de lijst, zoals in HR_BACKENDS=claude,codex,hermes, totdat dit door de ontwikkelaars is opgelost.

Heeft HarnessRouter internettoegang nodig bij de eerste start?

Ja. De agent-CLI's worden bij de eerste start opgehaald in plaats van meegeleverd in de image, omdat elk daarvan zijn eigen licentie bevat. Een machine zonder uitgaande route toont de installing-regels en bereikt vervolgens nooit ready on :3000. De download vindt eenmalig per volume plaats, dus latere starts duren enkele seconden en vereisen geen netwerk buiten de verbinding met de modelprovider die u heeft gekoppeld.

Ik ben het consolewachtwoord kwijt. Hoe krijg ik weer toegang?

Er is geen wachtwoordherstel via e-mail, omdat er geen accountsysteem of mailserver aanwezig is. Stop de container, verwijder /data/selfhost-auth.json uit het volume, start de container opnieuw, log in met de standaardgegevens en stel een nieuw wachtwoord in via de pagina Profile. Als de container en het volume beide harnessrouter heten, gebruikt u docker stop harnessrouter, gevolgd door docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json en daarna docker start harnessrouter.