Two Host-mode Containers Clashed on Port 8642
Two Hermes containers with network_mode: host fought over port 8642. Fix: a single gateway run container with HERMES_DASHBOARD=1, dashboard in-process.
Symptom: two containers, one port 8642
This morning both my hermes and hermes-dashboard containers were dead on arrival. The log was short but unambiguous: api_server failed to bind 0.0.0.0:8642 with address already in use. My first guess was a bug in the latest image — the last push was just a routine docker compose pull. But re-reading the compose file revealed the real cause: both services used network_mode: host.
Why network_mode: host causes a port clash
With network_mode: host, a container gets no network namespace of its own; it uses the host's network stack directly. Two consequences follow: ports: are ignored (no NAT), and any process that binds a port occupies that port on the host. So if two host-mode containers both run a gateway listening on 8642, the second one fails. The Compose docs state it plainly: two host-mode containers wanting the same port collide, and the second dies with bind: address already in use.
In my case, hermes-dashboard was not just a panel — it also spins up its own gateway + api_server because it needs gateway-liveness detection. Two gateways, two api_servers, one port. Collision.
Fix: one container, in-process dashboard
The fix is simple and actually simplifies things: run a single gateway run container with HERMES_DASHBOARD=1. Per the Hermes Docker docs, the dashboard already runs as a supervisor-managed s6-rc service inside the same container as the gateway — no separate container needed. To bind it to loopback so the auth gate does not engage, set HERMES_DASHBOARD_HOST=127.0.0.1 (the in-container default is 0.0.0.0, and the dashboard docs note the auth gate auto-engages on a non-loopback bind).
The final compose looks like this:
services:
hermes:
image: nousresearch/hermes-agent:latest
container_name: hermes
restart: unless-stopped
environment:
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
- TZ=Asia/Jakarta
- HERMES_DASHBOARD=1
- HERMES_DASHBOARD_HOST=127.0.0.1
command: ["gateway", "run"]
The old dashboard service is gone. No more two host-mode containers. Before/after:
After:
Why this is better, not just shorter
The Hermes docs have a section for this — “Why one container with many profiles, not many containers” — and the reasoning holds for a personal setup: one image, one venv, one Playwright cache, instead of N duplicates per container. Before the s6 migration, the “one container per profile” pattern was necessary because there was no in-container supervisor. Now s6 is PID 1, so a single container is enough to run the gateway plus a dashboard that auto-restarts on crash.
The lesson for other Docker setups: network_mode: host is a double-edged sword. It removes NAT overhead (handy for a monitoring agent or DNS), but turns port collisions into a host-level problem, not a container-level one. If you need two processes that both bind a port, prefer a bridge network with explicit ports:, or merge them into one container like I did.
Wrap-up
This morning's bug was not in the image — it was in the assumption: two host-mode containers can never share port 8642. Merging them into one container with HERMES_DASHBOARD=1 + a 127.0.0.1 bind fixed the crash and shrank the compose file. For more on safely self-hosting the dashboard, see this blog's embed notes or the multi-stage Docker write-up.