Skip to content
Consultation

Two Host-mode Containers Clashed on Port 8642

Adityo Guni Waluyo

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.

Related articles