SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-09-08

HarnessRouter zelf hosten: één API voor alle agents

Host Codex, Claude Code en Hermes achter één API met HarnessRouter. Leer de exacte Docker-deploy, de loopback-configuratie, de verplichte login-wijziging 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 komt met een eigen installatie, een eigen formaat voor inloggegevens en een eigen definitie van wat een sessie is. HarnessRouter draait ze allemaal binnen één container en plaatst er één HTTP-endpoint, één login en één geheime opslag voor.

Dat is het hele idee, 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 er één worden. Als u vandaag precies één harness draait, is dit een slechtere configuratie dan het direct installeren van dat harness. Die afweging staat in de laatste sectie, dus lees deze voordat u implementeert.

Alles hieronder is gecontroleerd tegen image-tag 0.5.5, opgehaald op 19 augustus 2026. Het project publiceert bijna dagelijks nieuwe tags, dus controleer de tag die u daadwerkelijk draait in plaats van deze pagina over een maand 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 terwijl deze wordt uitgevoerd, 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. De website noemt dit 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 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.

Wat u nodig heeft voordat u begint

Docker en ongeveer 4 GB vrije schijfruimte. U heeft tevens een API-sleutel nodig van een modelprovider waarvoor u reeds betaalt. De image-download is ongeveer 700 MB; de resterende schijfruimte wordt gebruikt door de agent-CLI's en de werkmappen waarin zij schrijven. Er is geen model meegeleverd en de image bevat geen proefsleutel, waardoor taken falen 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

Wacht vervolgens tot de container is opgestart. De eerste start is traag; 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 latere start slechts enkele seconden duurt en er geen installatieregels meer worden getoond.

Uit deze download volgen twee feiten die beide relevant zijn op een VPS. Ten eerste: de eerste boot vereist uitgaande netwerktoegang. De image is niet volledig zelfstandig; een server achter een egress-filter of een server zonder route naar buiten blijft hier hangen en zal nooit ready on :3000 tonen. Het proces faalt bij de eerste start, niet bij docker pull, wat een verwarrend punt 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. Alle duurzame data bevindt zich in /data: de SQLite-databases, de opgeslagen bestanden, de secret store en de agent-workspaces. Als u dit 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 wordt geschreven kan resulteren in een bestand dat niet kan worden geopend. Dezelfde discipline van stoppen-vóór-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 start.

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 is belangrijk omdat er tussen 9 en 18 augustus 2026 zestien versie-tags zijn uitgebracht, en een agent-runtime die onverwacht wijzigt, is lastig te debuggen. Kopieer vervolgens het omgevingsbestand, beveilig de rechten ervan 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 modus 600 is het minimum. Als het subcommando docker compose onbekend is, behandelt de spiekbrief voor Docker Compose-commando's 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 wat u heeft ingesteld:

docker port harnessrouter
sudo ss -ltnp | grep 3000

ss afdrukken van 127.0.0.1:3000 is correct. 0.0.0.0:3000 betekent dat de console op het openbare internet staat. Dat is hier 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. Het bevat ook de provider-key waarmee u verbinding heeft gemaakt. Iedereen die een onbeveiligde console bereikt, kan uw werk inzien, opdrachten 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 die ufw beheert, wordt geraadpleegd. Een gepubliceerde poort blijft dus bereikbaar, zelfs wanneer sudo ufw status deze als geweigerd markeert. Test vanaf een andere machine, niet vanaf de VPS, anders test u niets. Dit is dezelfde les als bij het headless draaien van dsh op poort 3080: bind de service aan loopback 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 gescripte 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 en start opnieuw op. 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 tot "een systeem dat niemand anders kan bereiken". 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, aangezien oudere versies geen beveiligingspoort hebben

Dit is een cruciaal onderdeel. Versies 0.1.x en 0.2.0 werden uitgebracht zonder enige 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. Een oude, vastgezette tag of een compose-bestand dat van een collega is gekopieerd, kan er dus voor zorgen dat er vandaag de dag een onbeveiligde console op een publieke poort draait.

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, niet pas op een later tijdstip. Bij alles op of boven die versie 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 verbinden

Niets werkt totdat er een modelprovider is verbonden. 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 zet 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 het voor problemen zorgt: elke waarde waarbij hermes ontbreekt, 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 het draaien van de Hermes agent op een eigen VPS 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-telling 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 aangepaste verbinding hierboven is de manier waarop u een harness koppelt aan een OpenAI-compatibel eindpunt dat u reeds host, op de wijze waarop een zelfgehoste DeepSeek-harness op een VPS is geconfigureerd.

Bereik de service vanaf uw laptop zonder een poort open te stellen

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

Een SSH-tunnel is de eenvoudigste 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 juiste oplossing wanneer anderen ook toegang moeten krijgen. De proxy beheert het TLS-certificaat (transport layer security) en stuurt het verkeer door naar de loopback-interface. De README bevat een Caddy-configuratie:

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 over het hoofd wordt gezien. De agent verwerkt stream-tokens gedurende enkele minuten. Een proxy die de respons buffert, houdt deze tokens vast totdat de beurt is voltooid, waardoor de console bevroren lijkt en vervolgens alle output tegelijk toont. 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 kiezen van de juiste oplossing voor uw server.

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-toegang 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, en ten minste 65536 ondergeschikte UIDs in /etc/subuid en /etc/subgid voor de gebruiker. uidmap zit in het Ubuntu-archief, maar docker-ce-rootless-extras niet: dit wordt geleverd vanuit de eigen apt-repository van Docker op download.docker.com, die door de installatie van de Docker engine wordt toegevoegd. Als u de engine niet vanuit die repository heeft geïnstalleerd, geeft grep -rl download.docker.com /etc/apt/sources.list.d/ geen uitvoer en zal de onderstaande installatie het pakket niet vinden.

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 hier niet optioneel. Zonder deze optie stopt de systemd-instantie van de gebruiker wanneer de laatste sessie wordt gesloten, waardoor de container stopt zodra u uitlogt. Bevestig het resultaat met docker info, die rootless onder Security Options vermeldt. Rootless-modus kan zonder extra configuratie geen poorten onder 1024 binden, wat hier niet uitmaakt omdat poort 3000 boven die grens ligt. Het opzetten van het account zelf wordt behandeld in het aanmaken van gebruikers met minimale rechten 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 miste 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 niets kan uitvoeren.

De console bevriest halverwege een antwoord achter een proxy. De output verschijnt in één blok zodra de beurt eindigt. 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; deze 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 endpoint en één opslaglocatie 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 het harness een configuratiewaarde is in plaats van een herschreven implementatie. Dat is wat UHP u oplevert, met de kanttekening hierboven 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 login tussen u en de tool. Het is ook de verkeerde aanpak als u wilt dat meerdere 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 alle transcripten, 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 benader de console via een SSH-tunnel of een reverse proxy met TLS-termination. Een host-firewall is op zichzelf niet voldoende: Docker schrijft eigen regels naar de nat-tabel van de kernel, waardoor een gepubliceerde poort reageert vanaf het internet, zelfs als ufw aangeeft dat deze is geweigerd. Controleer dit met sudo ss -ltnp | grep 3000; dit zou 127.0.0.1:3000 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 vorm van authenticatie. Beide tags zijn nog steeds gepubliceerd en kunnen worden opgehaald, waardoor iedereen die deze versies draait, ervan afhankelijk is dat niemand de poort ontdekt. 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 bij een actuele 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 er verschijnt niets nuttigs in docker logs. Houd hermes in de lijst, zoals in HR_BACKENDS=claude,codex,hermes, totdat dit upstream 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 een eigen licentie heeft. Een machine zonder uitgaande route toont de installing-regels en bereikt daarna nooit ready on :3000. De download vindt eenmalig per volume plaats, waardoor latere starts slechts enkele seconden duren en geen netwerkverbinding vereisen, behalve met de modelprovider die u heeft gekoppeld.

Ik ben het console-wachtwoord 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 de naam harnessrouter hebben, 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.