Dispatch to a remote Docker daemon
Run agora dispatches from one machine (the orchestrator) while the worker containers execute on a second machine (the target) over its Docker daemon. This is the “inward dispatch” leg of the substrate topology: the orchestrator reaches a remote Docker daemon to run workers.
The hop runs over SSH — LocalDockerProvider honors the standard
DOCKER_HOST environment variable, so pointing it at ssh://user@target
is all the wiring needed. The walkthrough below uses a Windows target as a
worked example, but nothing here is Windows-only; the genuinely
Windows-specific steps are called out in asides.
A. On the target machine (runs the workers)
Section titled “A. On the target machine (runs the workers)”-
Docker running, with the worker image present locally. Get it there by any of: build from the repo (
docker build -t ghcr.io/quarrysystems/agora-worker:latest -f docker/agora-worker/Dockerfile .),docker saveon the orchestrator →docker loadon the target, or pull the GHCR digest (needs auth if the package is private). -
Enable an SSH server. On Linux/macOS this is the standard
sshd. -
Set up key auth. Put the orchestrator’s public key on the target.
-
✅ Confirm Docker is reachable over SSH (run from the orchestrator):
Terminal window ssh user@target "docker version"If this prints the remote engine, you’re good. If
dockerisn’t found, its PATH isn’t visible to the non-interactive SSH shell — fix that first.
B. On the orchestrator (this machine issues dispatches)
Section titled “B. On the orchestrator (this machine issues dispatches)”$env:DOCKER_HOST = "ssh://user@target"docker -H ssh://user@target ps # ✅ should list the TARGET's containersLocalDockerProvider reads DOCKER_HOST automatically — no code change needed.
To be explicit instead, inject the client in your agora config:
import Docker from 'dockerode';new LocalDockerProvider({ docker: new Docker({ protocol: 'ssh', host: 'target', username: 'user' }) })C. Smoke test
Section titled “C. Smoke test”Run a hello-world-style dispatch, then check docker ps on the target — you
should see the agora-worker container running there while the orchestrator’s
CPU stays flat. That separation is the whole point.
D. Fallback if ssh:// misbehaves (common on Windows)
Section titled “D. Fallback if ssh:// misbehaves (common on Windows)”dockerode’s ssh:// transport runs docker system dial-stdio on the remote,
which is finicky against Windows Docker Desktop’s named pipe. Secure fallback:
-
In Docker Desktop on the target, expose the daemon on
tcp://localhost:2375(localhost only — never bind it to the LAN). -
From the orchestrator, tunnel it over SSH:
Terminal window ssh -L 2375:localhost:2375 user@target# then, in another shell on the orchestrator:$env:DOCKER_HOST = "tcp://localhost:2375"
The daemon port is bound only to the target’s localhost; SSH encrypts the hop. This sidesteps the named-pipe / dial-stdio path entirely.
E. Storage
Section titled “E. Storage”- Smoke-test first with the client and daemon on the same machine to prove
the
DOCKER_HOSTpath in isolation. - For a real cross-machine split, switch to
S3StorageProvider.LocalStorageProviderbind-mounts host paths that live on the target machine, so they won’t exist on the orchestrator — local FS storage does not compose across machines. Memory (if using the Stoa substrate) comes over HTTPS, not a bind-mount, so it is unaffected.
Next steps
Section titled “Next steps”- Deploy to Fargate + S3 — take the same dispatch flow onto production AWS compute and S3 storage.
- Architecture overview — the inward vs outward dispatch topology and the privilege boundary between compute hosts.
- Sandboxing AI agents — what the Docker isolation boundary guarantees and what it does not.