Install on the brain·
These steps are what produced the reference deployment on the Jetson AGX Thor. Nothing here is Jetson-specific: any arm64/amd64 docker host works.
1 · Clone·
git clone https://github.com/cagataycali/scout-the-rover.git
cd scout-the-rover
cp .env.example .env # fill it in — see Configuration
The Earth Rovers SDK is not vendored (it is .gitignored). The compose sdk service and the make sdk target both use
our fork, which adds the auto-join, the video watchdog and the fixed Chrome profile directory the containers rely on:
make sdk # git clone cagataycali/earth-rovers-sdk into ./earth-rovers-sdk + venv
$EDITOR earth-rovers-sdk/.env # SDK_API_TOKEN, BOT_SLUG, CHROME_EXECUTABLE_PATH, SDK_PORT=8002
Why 8002?
Port 8001 is often taken (docker desktop, other SDKs). Everything in this repo assumes the SDK on 8002 —
keep ROVER_SDK_URL and the SDK's own SDK_PORT in agreement.
2 · Build the image·
What goes into scout:slim (Dockerfile.scout.slim):
- Debian
python:3.12-slim(musl/Alpine is avoided on purpose: lerobot/opencv/numpy/cryptography wheels). chromiumfor the SDK's headless capture, ALSA/PortAudio for voice,mkcerttooling, fonts.- CPU-only torch first, then
requirements.txt; lerobot only whenINSTALL_LEROBOT=1(default). - node 22.14 + tiny-tech vendored in
/opt/tiny-mcpso the fleet bridge needs no runtime npm fetch (off unlessTINY_MCP=1). - One
docker/entrypoint.shthat dispatches on the service name:sdk dashboard telegram thinker listener media yolo agent voice reasoner warmup all.
3 · Bring the stack up·
make docker-slim-up-all # --profile all: sdk + dashboard + telegram + thinker (+ media + yolo via the override)
docker compose -f docker-compose.slim.yml -f docker-compose.slim.override.yml --profile all ps
The dashboard waits for sdk to be healthy (the SDK health check accepts 200/400 from /), then serves
https://<host>:8080. First visit → the passkey enrolment screen (Auth).
The voice persona is a separate profile because it grabs the rover's mic and speaker:
docker compose -f docker-compose.slim.yml -f docker-compose.slim.override.yml --profile voice up -d voice
4 · (Optional) the host-side persona supervisor·
The cockpit's 🎭 Personas pills start/stop the voice, thinker and telegram containers without a docker socket in any
container. A ~250-line stdlib server on the host listens on a unix socket that is bind-mounted into the dashboard only:
# deploy/scout-supervisor.service — edit WorkingDirectory/ExecStart paths for your user
mkdir -p ~/.config/systemd/user && cp deploy/scout-supervisor.service ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now scout-supervisor
echo "SCOUT_SUPERVISOR_TOKEN=$(openssl rand -hex 24)" >> .env # shared with the dashboard via env_file
Details and the threat model: Personas.
5 · (Optional) a public hostname·
cloudflared tunnel login
cloudflared tunnel create scout
cloudflared tunnel route dns scout scout.example.com
# ~/.cloudflared/config.yml
tunnel: <UUID>
credentials-file: /home/you/.cloudflared/<UUID>.json
ingress:
- hostname: scout.example.com
service: https://localhost:8080
originRequest:
noTLSVerify: true # scout's own cert is self-signed; Cloudflare serves real TLS publicly
- service: http_status:404
Set SCOUT_PUBLIC_URL=https://scout.example.com and SCOUT_AUTH_RP_ID=scout.example.com so passkeys bind to the public
name. The tunnel exposes only the dashboard; the SDK (:8002) and media hub (:8090) stay on the LAN.