name: hd-networking description: > Port architecture, hostnames, and networking rules for Hydrogen Desktop. MUST READ before adding ports, exposing a service to your macOS host, referencing host URLs from inside the workspace, or wiring any service communication. Trigger words — HD port, HD network, port mapping, proxy, 127.0.0.1, loopback, adom-host gateway, hd-control-url, VSCODE_PROXY_URI, relay URL, code-server proxy, container networking, ADOM_CARBON_URL, ADOM_HYDROGEN_URL, direct connect, 8770, 7380.

Hydrogen Desktop — Networking & Port Architecture

This is the machine-runtime version (default). For the legacy Docker container runtime (HD_RUNTIME=docker) see the docker/ bucket.

Core Principle: expose 0.0.0.0 listeners to the Mac

The HD workspace is the Adom-Workspace machine (a systemd-nspawn machine booted inside the hd-builder Lima VM), not a Docker container. macOS does not do WSL2-style magic loopback mirroring — vz does NOT mirror loopback. Instead, Lima exposes the machine's listeners to the Mac: any service that binds 0.0.0.0:<port> inside the workspace machine is reachable at 127.0.0.1:<sameport> on the Mac — no Docker -p mapping, no proxy hop required. The port number is the same on both sides.

The two corollaries that drive everything below:

  • Bind 0.0.0.0, not 127.0.0.1. A 127.0.0.1-only listener inside the machine is NOT exposed to the Mac — the Mac can't see it. A workspace service that binds loopback-only is invisible to the host until you route it explicitly (see hd-port-watcher).
  • There are NO Docker port mappings. code-server binds 0.0.0.0:7380 inside the machine and is therefore reachable at localhost:7380 on the Mac automatically. Same for a dev server on 0.0.0.0:5173localhost:5173.

In-machine services (no port mapping needed)

Machine Port Service Reachable on the Mac at
7380 code-server (VS Code) localhost:7380 (exposed by Lima)
8765 relay WS bind 0.0.0.0localhost:8765, or /proxy/8765/
8766 relay HTTP bind 0.0.0.0localhost:8766, or /proxy/8766/
5173 your dev server bind 0.0.0.0localhost:5173

All of these are also reachable through code-server's /proxy/<port>/ URL (see below), which remains the preferred path for browser-facing HTTP/WS.

Host-Side Ports (Tauri app, native macOS listeners)

These ports run on the macOS host inside the HD Tauri process. They are native TCP listeners on the Mac — not inside the workspace machine.

Port Service Notes
1420 Dev file server Dev mode only, serves build/
8770 adom-desktop direct connect Owned by Adom Desktop, NOT HD
47080 HD discovery server
47083 HD reverse proxy Proxies to code-server
47084 HD control API Health, setup, eval, CDP, /workspace/status, /port-forward
47085 WebView2 CDP Chrome DevTools Protocol

These are configurable via ~/Library/Application Support/hydrogen-desktop/ports.json.

Reaching a host service FROM the machine: use the adom-host gateway, NOT 127.0.0.1. macOS/vz does NOT mirror loopback, so a host listener on 127.0.0.1:<port> is not visible at the machine's own loopback. Instead the host binds 0.0.0.0, and the machine reaches it via the adom-host gateway hostname: adom-host:<port>. The machine's /etc/hosts maps adom-host to the gateway IP — no extra firewall rule needed. (host.docker.internal is a Docker-ism HD does NOT use — inside the machine it resolves to a different interface, not where HD listens, so curls to it fail; use adom-host instead.)

Hostnames — reach the host at adom-host

From inside the machine → host machine

adom-host

macOS/vz does NOT mirror loopback, so use the adom-host gateway alias to reach host listeners from inside the workspace machine. The machine's /etc/hosts maps adom-host to the gateway IP, so adom-host:<port> reaches the Mac's host listeners directly (the host binds 0.0.0.0). Used for:

  • ADOM_CARBON_URL=http://adom-host:{proxy_port}
  • ADOM_HYDROGEN_URL=http://adom-host:{proxy_port}
  • Direct connect probe: http://adom-host:8770/health
  • HD control API: the live URL in ~/.adom/hd-control-url (http://adom-host:<dynamic>)

The control port is dynamic per launch — read it from ~/.adom/hd-control-url (your non-interactive Bash shells don't source .bashrc/profile.d, so the env var alone is unreliable): BASE="$(cat ~/.adom/hd-control-url)".

From the host → machine services (via proxy)

http://127.0.0.1:{code_server_port}/proxy/{machine_port}/
ws://127.0.0.1:{code_server_port}/proxy/{machine_port}/

Example: relay health → http://127.0.0.1:7380/proxy/8766/health Example: relay WS → ws://127.0.0.1:7380/proxy/8765/

This path always works for 0.0.0.0 AND 127.0.0.1-bound machine services, because code-server itself is inside the machine and proxies to them locally. It is the preferred path for browser-facing HTTP/WS even though direct exposure also works for 0.0.0.0 listeners.

From the host → host-side services

http://127.0.0.1:{port}/

Control API → http://127.0.0.1:47084/health Direct connect → http://127.0.0.1:8770/health

Relay Architecture

The relay (adom-desktop serve) runs inside the machine on ports 8765 (WebSocket) and 8766 (HTTP). It binds 0.0.0.0, so it is reachable both via Lima's port exposure (localhost:8765 / localhost:8766 on the Mac) and via the code-server proxy. The proxy path is preferred for connection stability:

  1. adom-desktop on the Mac connects as a WS client via the code-server proxy: ws://127.0.0.1:7380/proxy/8765/
  2. Registration happens via server_add on the direct connect API (port 8770):
    {
      "name": "HD Local Machine",
      "url": "ws://127.0.0.1:7380/proxy/8765/",
      "autoConnect": true
    }
    
  3. Health checks use the proxy path: http://127.0.0.1:7380/proxy/8766/health

This matches cloud containers where adom-desktop connects via wss://<slug>.adom.cloud/proxy/8765/.

VSCODE_PROXY_URI

Inside the machine, VSCODE_PROXY_URI is set by code-server:

https://<slug>.adom.cloud/proxy/{{port}}/     (cloud)
http://localhost:7380/proxy/{{port}}/          (HD local, approximate)

Tools that build proxy URLs should use VSCODE_PROXY_URI when available, falling back to http://127.0.0.1:{code_server}/proxy/{port}/.

In-machine command form

The workspace is the Adom-Workspace machine inside the hd-builder Lima VM, so you reach it by entering its namespaces via nsenter, NEVER docker exec (<leader> is the machine's leader PID):

limactl shell hd-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 -- bash -lc '<cmd>'
# or the simpler form for a single command:
limactl shell hd-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 -- <cmd>

NEVER Do These

  1. NEVER bind 127.0.0.1-only for a service that must be reachable from the Mac. macOS won't expose it (vz does NOT mirror loopback). Bind 0.0.0.0, or register it with the port-forward registry (see hd-port-watcher).
  2. NEVER use host.docker.internal for machine→host communication — it's a Docker-ism HD doesn't use; inside the machine it resolves to the wrong interface. Reach the host via the adom-host gateway (vz does NOT mirror loopback, so 127.0.0.1 does NOT reach the host); read HD's live control URL from ~/.adom/hd-control-url.
  3. NEVER assume the host's 127.0.0.1 is reachable from inside the machine. macOS/vz does NOT mirror loopback — use the adom-host gateway alias (adom-host:<port>); the host binds 0.0.0.0 and the machine's /etc/hosts maps adom-host to the gateway. No firewall allow needed.
  4. NEVER docker exec / docker cp against the workspace. It's the Adom-Workspace machine; enter it via limactl shell hd-builder -- sudo nsenter -t <leader> -a -S 1001 -G 1001 --.
  5. NEVER hardcode port numbers in machine code. Read from env vars (ADOM_CARBON_URL, ADOM_HYDROGEN_URL) or discover via the direct connect API.
  6. NEVER assume adom-desktop direct connect (8770) is owned by HD. It's owned by Adom Desktop. HD's direct-api thread binds to 8770 as a fallback but Adom Desktop takes priority when both are running.