Skip to main content
ados-deploy is a Rust terminal tool in the Mission Control repo at tools/deploy/. It brings up the self-hosted stack (Convex backend and dashboard, the Mission Control web GCS, Mosquitto with its auth sync, and the video relay) through a guided flow, then keeps the lifecycle commands in one place. It is not a second stack. It drives the same Compose project documented on All-in-one stack, tools/selfhost/docker-compose.yml. It adds the ordering, the generated config, the secret handling and the verification.

When to use which path

ados-deploy works on a repo checkout, because it builds the Mission Control image from the repo and pushes Convex functions from it.

Invoke it

From a checkout, through the npm script:
Everything after -- goes to ados-deploy. The script runs cargo run --release --quiet --manifest-path tools/deploy/Cargo.toml --. Directly with cargo:
Through the bootstrap, which fetches a checkout if you do not have one:
The bootstrap uses the current checkout when it finds tools/selfhost/docker-compose.yml, otherwise it clones to $HOME/ADOSMissionControl. Override with ADOS_DIR and ADOS_REPO_URL. It then launches, in order of preference, a cached or committed ./ados-deploy binary, the matching release asset, or a cargo build. Extra arguments pass through. With no action the tool opens an interactive menu. On a non-terminal (CI, or a piped invocation) it asks you to pass an action instead.

Actions

An unrecognised action falls through to the menu.

Dry-run first

--plan (alias --dry-run) prints the files the deploy would write and the commands it would run, in order, then exits without touching anything. Secrets are redacted in the plan output. The order is the same one the real run executes.

Flags

Rendering and interactivity

Deploy answers

Supply these to run the whole flow without prompts. An unknown flag is ignored rather than rejected, so a newer wrapper script stays compatible with an older binary. ados-deploy does not set up TLS or a tunnel; see Expose the stack through a Cloudflare tunnel.

What a deploy runs

The plan is ordered and idempotent. For a full self-hosted deploy:
  1. Preflight: Docker running, ports free, Node present.
  2. Write the generated config files.
  3. Start the Convex backend and dashboard.
  4. Wait for Convex to answer on /version.
  5. Retrieve the admin key (docker compose exec -T convex-backend ./generate_admin_key.sh).
  6. Push the Convex functions.
  7. Generate and set the auth keys (JWT_PRIVATE_KEY, JWKS).
  8. Set the Convex env vars: SITE_URL, plus MQTT_BROKER_URL and VIDEO_RELAY_URL when those services exist.
  9. Set the relay secrets on Convex: MQTT_AUTH_RELAY_SECRET and VIDEO_RELAY_SECRET, for each service this deploy starts.
  10. Create the broker password file.
  11. Seed the broker ACL from tools/mqtt-bridge/deploy/acl.conf, only when absent.
  12. Build and start the remaining services.
  13. Verify the GCS, the video relay and the MQTT port are reachable.
With --convex-managed-url, steps 3 to 9 are skipped. With --mqtt-managed-url or --no-mqtt, steps 10 and 11 are skipped. On a plain-HTTP host the plan ends with a note that browser cloud mode needs HTTPS. Because the plan is idempotent, upgrade reconstructs the config from the deployed .env and runs it again.

Files it writes

Paths are relative to the repo root; commands run from the root.

Services

The tool starts the services in tools/selfhost/docker-compose.yml: convex-backend, convex-dashboard, mission-control, mosquitto, mqtt-auth-sync and video-relay. What each one does is on All-in-one stack. It checks for port conflicts before it starts anything; the default ports are in the port table.

Check and tear down

teardown runs docker compose down. Purging volumes is a separate opt-in, so a teardown does not destroy your Convex data. To roll back a bad upgrade, check out the previous commit of the repo and run upgrade again. The image is rebuilt and the functions re-pushed from the checkout, so the checkout is the version you run.

Next steps

All-in-one stack

Every step the tool runs, by hand.

Ports and troubleshooting

The port table and the common failure modes.
Last modified on October 11, 2026