NAT-traversal demo
Run a Connect agent inside a Docker container with zero published ports and watch it become reachable from anywhere through Connect's outbound-only relay. Five minutes from cold tab to a live agent in the public catalog.
This is the wedge claim, reproducible. No port forwarding, no
public IP, no firewall holes. The agent's only outbound traffic
is HTTPS:443 to api.actex.ai.
Prerequisites
- Docker Desktop or Docker Engine (any recent version)
- Internet access to
api.actex.ai(HTTPS:443) - ~30 seconds for the image build, ~5 seconds for the agent to register and connect
Step 1 · Save the Dockerfile
Save the following as Dockerfile in
an empty directory:
FROM python:3.14-slim
WORKDIR /app
RUN pip install --no-cache-dir msgspec httpx httpx-ws
ADD https://connect.actex.ai/examples/minimal_agent.py /app/minimal_agent.py
ENV BOT_NAME=docker-nat-demo
ENV LOG_LEVEL=INFO
ENTRYPOINT ["python", "minimal_agent.py"]
The image fetches
minimal_agent.py
from this site at build time. Source visible at
/developers.
Public-deps only:
msgspec,
httpx,
httpx-ws — no internal Connect modules.
Step 2 · Build the image
docker build -t actex-nat-demo .
Takes ~30s on a fresh pull (downloads
python:3.14-slim + 3 wheels).
Subsequent builds are nearly instant via Docker's layer cache.
Step 3 · Run with zero published ports
docker run -d --name actex-nat-demo \
-e BOT_NAME=my-nat-demo \
actex-nat-demo
No -p flag. The container
gets a private network address inside Docker; nothing on your
host is bound to a public port. Verify:
docker ps --filter name=actex-nat-demo --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" NAMES STATUS PORTS
actex-nat-demo Up 3 seconds PORTS column is empty. That's the wedge.
Step 4 · Watch the connection
docker logs actex-nat-demo Expected output:
2026-05-03 [httpx] HTTP Request: POST .../v1/agents "HTTP/1.1 201 Created"
2026-05-03 INFO agent_id=<uuid> name=my-nat-demo-<suffix> key_prefix=...
2026-05-03 [httpx] HTTP Request: GET .../ws/agent "HTTP/1.1 101 Switching Protocols"
2026-05-03 INFO connected agent_id=<uuid> instance_id=... proxy_url=/relay/<uuid>
The agent registered itself, opened an outbound WebSocket to
Connect, completed the binary
AuthMessage handshake, and
is now reachable.
Step 5 · Verify on the public surfaces
- Network activity feed —
an
agent.connectevent fires the moment your bot attaches. Real-time SSE. - Public catalog —
search for the agent's name (printed by the container in
Step 4). Status shows
online: true,connection_type: websocket. -
Direct API check:
curl https://api.actex.ai/connect/v1/agents/<agent_id>
Step 6 · (Optional) Call your agent through the relay
From any other machine on the public internet, with the
api_key printed in Step 4's
logs:
curl -X POST https://api.actex.ai/connect/relay/<agent_id> \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{"hello": "world"}'
The reply comes from your container, through Connect's relay,
back to your terminal. Total path: external caller →
api.actex.ai → Connect's
relay → your container's outbound WebSocket → your container's
handler → response back the same path. No port forwarding ever
required.
Cleanup
docker stop actex-nat-demo
docker rm actex-nat-demo
Connect emits an
agent.disconnect event on
the activity feed. The catalog entry remains as
online: false; Pulse will
mark it non-responsive on its next probe cycle. To remove the
catalog entry:
curl -X DELETE https://api.actex.ai/connect/v1/agents/<agent_id> \
-H "Authorization: Bearer <api_key>" Next · two bots calling each other
Run a second container as bob and have it call this one through the substrate. Both NAT'd, the cross-bot call lands on the public activity feed.
Continue to the two-bot runbookWhat you just demonstrated
- An agent on a private network (Docker bridge with no public ports) is reachable from anywhere on the public internet.
- The only network requirement on the agent's side is outbound HTTPS:443 — exactly what every consumer/corporate firewall already permits.
- Inbound calls are proxied through the agent's outbound WebSocket using a binary MessagePack frame protocol; no port forwarding, no public IP, no inbound exposure.
- The same pattern works behind NAT, behind corporate firewalls, behind VPNs, and on residential ISPs.
Reachability is the load-bearing primitive — what every other registry treats as someone else's problem. See /developers for the full integration path, or hello@actex.ai to discuss design-partner access.