Skip to content

Docs / Bring your own boxes

Bring your own boxes

Enrol a VM into your fleet: requirements, the join command, hardening, and what never touches the box.

The compute is yours. The control plane is ours.

That distinction is the whole of this page. developerz.ai is delivered as SaaS — there is no self-hosted edition of the platform, and there will not be one. What is self-hosted is the part that does the work: AI-dev sessions, CI jobs and code review all execute on VMs you own, enrolled into your fleet. We dispatch; your hardware runs.

What a box needs

Checked by the installer before anything is downloaded, and re-checked server-side before your enrolment token is spent:

Requirement Value
OS Ubuntu (the installer refuses anything else)
Architecture x86_64, aarch64 or arm64
RAM ≥ 3800 MiB — a nominal 4 GB VM qualifies
Free disk on / ≥ 20 GiB
Privileges root, or sudo without a password prompt
Docker required (installed for you if absent)

Inbound ports: none. The runner dials out; nothing listens for us. You may keep SSH open for yourself, and the installer's default hardening will make sure your detected SSH port stays allowed before it turns the firewall on.

Outbound, the box needs to reach the fleet transport (nats.developerz.ai:4222, TLS), github.com, and whichever model endpoint your key uses.

Enrolling

Two transports. Pick the one that matches how you like to hand over trust.

1. Token join — you run one command

Mint an enrolment token in the dashboard under Fleet → Add box, then on the VM:

read -rsp 'Enrollment token: ' DZ_ENROLL_TOKEN && DZ_JOIN="$(mktemp)" && \
  curl -sfL -H "X-Dz-Enroll-Token: $DZ_ENROLL_TOKEN" \
    https://api.developerz.ai/join -o "$DZ_JOIN" && bash "$DZ_JOIN"

The token travels in a header, not in the URL, so it does not land in proxy logs or shell history the way a query parameter would. The script is downloaded to a temp file and then executed — deliberately not curl … | bash, so a truncated download cannot run half an installer.

The script gates on the requirements above, installs git, unzip, gnupg, gh, Docker and Bun, applies the baseline firewall, writes its credentials to /etc/dz-runner (mode 0700, files 0600), and enrols.

2. SSH pull — you hand over coordinates, we dial in

If you would rather not paste a secret into a shell, register the host and let the platform connect:

curl -X POST https://api.developerz.ai/v1/runners/enrollments/ssh \
  -H "Authorization: Bearer dev_pat_…" \
  -H "content-type: application/json" \
  -d '{"host":"box1.example.com","user":"root"}'

Scope: write:runners. The operator never holds a token in this mode — it is minted and consumed inside the platform, and it is not in the response.

Either way the box appears in Fleet → Boxes once it reports in.

Hardening

The installer runs a hardening pass by default (DZ_HARDEN=0 opts out, and says so in its output rather than silently skipping):

  • UFW: your detected SSH port(s) and 22 are allowed first, then default deny inbound / allow outbound, then enable.
  • Docker bypass closed: Docker publishes ports by writing iptables rules that sit in front of UFW. A DOCKER-USER block is injected into /etc/ufw/after.rules that returns for established traffic, loopback and RFC 1918, then drops. This is the difference between "I enabled a firewall" and "my Postgres container is not on the public internet".
  • Exposed-service scan: common data-store ports are checked for non-loopback listeners and reported. Nothing of yours is stopped or reconfigured — the scan tells you, you decide.

The runner itself runs as an unprivileged dz-agent system user under a locked-down systemd unit: no new privileges, an empty capability set, ProtectSystem=strict, private /tmp, and a memory/CPU ceiling.

What never touches the box

  • The GitHub App private key. It is not on your box and not on the runner path at all. CI jobs receive a repo-scoped installation token minted just in time with a 15-minute TTL.
  • Plaintext secrets. Vault entries in a task assignment are encrypted blobs; the assigner that routes them does not decrypt them.
  • Your source, permanently. Work happens in a workspace on your own disk.

Updates

The mothership pushes an update request; the runner unsubscribes, drains in-flight work (bounded at 30 seconds), downloads the new binary and its detached signature, verifies the Ed25519 signature, atomically replaces itself, and exits into Restart=always.

There is no git pull, no build step on your VM, and no unsigned artefact path. If the signature does not verify, the update does not happen.

Presence and draining

Each box heartbeats every 30 seconds into a key with a 90-second TTL — liveness is the key existing, not a timestamp in a table. On a graceful drain the heartbeat continues until in-flight work settles and the key is then left to lapse rather than deleted, so nothing can be double-executed mid-drain.

To take a box out of rotation:

box drain <box_id>     # MCP verb; in-flight tasks are re-enqueued

To retire it for good, revoke it: POST /v1/runners/:id/revoke.

How many boxes you may enrol

One AI-dev seat allows three enrolled servers, inclusive of the box running the AI-dev work itself — the other two are yours for CI, tests and review at no extra charge. Boxes are fungible. Trying to enrol past the cap returns 402 Payment Required rather than quietly accepting the box; see billing.