From 6a0556579ff56582cc2a1895013971a151562f62 Mon Sep 17 00:00:00 2001 From: Andres Berrios Date: Wed, 15 Jul 2026 19:02:40 +0200 Subject: [PATCH] docs(agents): explain Docker webhook networking (0.0.0.0 bind + loopback rewrite) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime server runs in Docker while the app runs on the host, so webhook callbacks cross the Docker host boundary. Two behaviors are load-bearing but undocumented, and both fail silently: - The app must listen on all interfaces (0.0.0.0). An app bound to loopback only (the default for some frameworks, e.g. Nuxt/Nitro) is unreachable from the container: the agent spawns but never responds, with no error surfaced. The walkthrough's @hono/node-server example happens to bind all interfaces, masking the requirement. - A serveEndpoint host of localhost/127.0.0.1/::1 is rewritten to host.docker.internal before webhook dispatch (via ELECTRIC_AGENTS_REWRITE_LOOPBACK_WEBHOOKS_TO, set by the CLI compose). This is why `agents types` shows host.docker.internal for a localhost serveEndpoint — previously unexplained and confusing. Add a warning callout to the app-setup reference and the walkthrough, covering the 0.0.0.0 requirement, the loopback rewrite, and the on-host caveat (variable unset when not running in Docker). --- website/docs/agents/usage/app-setup.md | 10 ++++++++++ website/docs/agents/walkthrough.md | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/website/docs/agents/usage/app-setup.md b/website/docs/agents/usage/app-setup.md index 50f752ffba..e1a2a397e9 100644 --- a/website/docs/agents/usage/app-setup.md +++ b/website/docs/agents/usage/app-setup.md @@ -115,6 +115,16 @@ server.listen(PORT, async () => { }) ``` +::: warning Local Docker networking + +In local development the runtime server runs in a Docker container (started by the CLI) while your app runs on the host. Two things make webhook callbacks reach your app: + +1. **Your app must listen on all interfaces (`0.0.0.0`), not just loopback.** The container reaches your app across the Docker host boundary, so an app bound only to `127.0.0.1`/`localhost` is unreachable — the agent spawns but never responds, and **no error is surfaced**. Node's `http` server and `@hono/node-server` listen on all interfaces by default; some frameworks default to loopback only (e.g. Nuxt/Nitro, which needs `devServer.host: '0.0.0.0'`). + +2. **Loopback `serveEndpoint` URLs are rewritten automatically.** A `serveEndpoint` whose host is `localhost`, `127.0.0.1`, or `::1` is rewritten by the server to the Docker host (`host.docker.internal`) before it dispatches webhooks — which is why a configured `http://localhost:3000/webhook` shows up as `http://host.docker.internal:3000/webhook` in `electric-ax agents types`. This is controlled by `ELECTRIC_AGENTS_REWRITE_LOOPBACK_WEBHOOKS_TO`, which the CLI's docker-compose sets to `host.docker.internal`. If you run the runtime server **directly on the host** (not in Docker) the variable is unset, so loopback URLs are left as-is — leave it unset in that case, or on-host loopback callbacks will break. + +::: + ## registerTypes Registers all entity types with the Electric Agents runtime server and creates webhook subscriptions. Uses upsert semantics — re-registering an existing type updates it rather than erroring. diff --git a/website/docs/agents/walkthrough.md b/website/docs/agents/walkthrough.md index 78d89f1e1e..053217e652 100644 --- a/website/docs/agents/walkthrough.md +++ b/website/docs/agents/walkthrough.md @@ -349,6 +349,15 @@ Then when you run the app you should see: INFO: [agent-runtime] Registered entity type: assistant ``` +::: warning How the container reaches your app + +The runtime server runs in Docker while your app runs on the host, so webhook callbacks cross the Docker host boundary. Two things make this work — and both are easy to trip over: + +- **Your app must listen on all interfaces (`0.0.0.0`), not just loopback.** The `@hono/node-server` `serve()` above already does this, so the walkthrough works as-is. But if you wire the runtime handler into a framework that binds to loopback only by default (e.g. Nuxt/Nitro), the container can't reach it — the agent spawns but never responds, with **no error**. Configure the framework to listen on `0.0.0.0` (for Nuxt: `devServer.host: '0.0.0.0'`). +- **`localhost` in `serveEndpoint` is rewritten to `host.docker.internal` automatically.** That is why the `agents types` output below shows `http://host.docker.internal:3000/electric-agents` even though we configured `http://localhost:3000`. The rewrite is controlled by the `ELECTRIC_AGENTS_REWRITE_LOOPBACK_WEBHOOKS_TO` env var, which the CLI's docker-compose sets. If you run the runtime server directly on the host instead of in Docker, it is unset and loopback URLs are used as-is. + +::: + Check the registered entities types on the command line: ```sh