Skip to main content

Overview

Remote devboxes let internal root workspace members offload expensive local work to self-hosted runner machines. A runner connects outbound to tuturuuu.com, receives a synced dirty checkout, runs commands in an isolated container, streams logs, stores artifacts, and releases the lease automatically for one-off work. V1 is intentionally self-hosted. It does not provision cloud machines. Any runner host should have Node.js, Bun, Docker, and Git available.

Quick Start

ttr login
ttr box doctor
ttr box setup
ttr box setup --dir .
ttr box setup --dir . --clone-into ./tuturuuu
ttr box agent register --name "ci-lab-1"
ttr box agent start --token <runner-token>
ttr box repair --dir .
ttr box upgrade --runner <runner-id>
TUTURUUU_DEVBOX_RUNNER_TOKEN=<runner-token> ttr box shutdown
ttr box setup is the default bootstrap for a runner host. It first checks whether the current directory is already a Tuturuuu platform checkout. If it is, setup reuses that checkout. Otherwise it can clone or reuse https://github.com/tutur3u/platform.git, install dependencies with bun install --frozen-lockfile, start local Supabase with bun sb:start, verify supabase status -o json, and write local Supabase connection values into ignored apps/*/.env.local files. Secret values are redacted from human and JSON output. Pass --dir <path> for a specific checkout. If the target exists but is not a valid Tuturuuu checkout, interactive setup asks whether it should clone into a nested tuturuuu directory. In non-interactive shells, pass --clone-into <path> explicitly:
ttr box setup --dir . --clone-into ./tuturuuu
Use --yes only when the host should install detected missing prerequisites automatically. Runner registration and service installation are separate choices. To register the machine and install a boot-starting runner service in one non-interactive command after login:
ttr box setup --agent --service --runner-name "$(hostname)-devbox" --yes
The setup command stores the runner token in a restricted local env file and installs a system-level runner service on Linux systemd or macOS launchd. The service starts after reboot and restarts automatically if the runner exits. Runner heartbeats report a small observability snapshot, including ttr, Bun, Node, Docker, Git, OS, CPU, RAM, load average, and uptime. Root workspace admins can inspect those fields from Infrastructure > Devboxes. If a host completed ttr box setup but does not appear in Infrastructure > Devboxes, confirm it was registered as a runner. Plain setup prepares the checkout, dependencies, local Supabase, and ignored env files only; it does not create a private.devbox_runners row. Run setup with --agent --service, or register and start the agent manually, then confirm ttr whoami on that host is pointing at the same production origin and root workspace user. If the host appears as registered but shows no heartbeat, inspect the installed service on that host:
sudo systemctl status --no-pager --full tuturuuu-devbox-runner.service
sudo journalctl -u tuturuuu-devbox-runner.service -n 80 --no-pager
The runner service reads its token from the local devbox-runner.env file. If the journal shows a missing runner token or stale wrapper, upgrade the CLI and repair the service. Repair reuses the existing token file; it does not register a new runner token or create another runner row.
ttr upgrade
ttr box repair --dir .
Use dry-run first when you want to confirm the checkout, token file, service manager, wrapper path, and service path without writing files or calling sudo:
ttr box repair --dir . --dry-run
Upgrade a runner’s global CLI through the same brokered queue:
ttr box upgrade --runner <runner-id>
The upgrade command queues bun i -g tuturuuu for the selected runner, waits for completion, and returns the remote command logs and exit code. Remove a runner from the cluster from the runner host with its runner token:
TUTURUUU_DEVBOX_RUNNER_TOKEN=<runner-token> ttr box shutdown
Shutdown deletes the runner’s server-side token rows and marks the runner revoked. If the host installed a systemd or launchd runner service, stop and disable that service as well so the OS does not keep restarting an invalid agent process. Run heavy commands remotely:
ttr box run -- bun check
ttr box run -- bun sb:reset
ttr box run -- bun test:e2e
ttr box build --cwd apps/web
By default, ttr box run creates an auto lease, waits for an authenticated runner to claim the job, streams recorded logs after completion, and releases the lease after one-off work. Use --keep for repeated sync/run work:
ttr box run --keep --preview-port 7803 -- bun test:e2e
ttr box sync --lease <lease-id> --watch
ttr box preview --lease <lease-id> --port 7803
ttr box release <lease-id>

Build, Serve, And Tunnel

Use ttr box build for common build workloads without spelling the full remote command every time:
ttr box build
ttr box build --cwd apps/web
ttr box build --build-command "bun turbo:local run build -F @tuturuuu/web"
Use ttr box serve when the remote devbox should build an app, start the server, and keep the run alive. It defaults to apps/web on port 7803, stores the preview port on the run, and returns immediately unless --wait is passed:
ttr box serve --cwd apps/web --port 7803
ttr box preview --lease <lease-id> --port 7803
ttr box logs <run-id>
ttr box stop <run-id>
ttr box build, ttr box serve, and ttr box tunnel do not set a remote command timeout by default. Pass --timeout <duration> only when the run should be killed automatically. To expose a served app through a Cloudflare tunnel, keep the token in a local environment variable and pass the variable name to the devbox. The queued command references $CLOUDFLARED_TOKEN; the raw token is delivered only as run-scoped env and is redacted from logs.
export CLOUDFLARED_TOKEN=<token-from-cloudflare>
ttr box serve --cloudflared --cloudflared-token-env CLOUDFLARED_TOKEN
ttr box tunnel --cloudflared-token-env CLOUDFLARED_TOKEN
ttr box tunnel runs a dockerized cloudflare/cloudflared container with host networking on the runner. The command policy blocks inline cloudflared --token ... arguments, so operators should use --cloudflared-token-env or another local environment variable indirection rather than pasting raw tokens into commands.

Container Boundary

Commands run in a per-lease Docker container or Compose project. The runner mounts only the synced workspace and named cache volumes. Host execution is not exposed in v1, and arbitrary host path mounts are blocked unless an operator explicitly allowlists them. Allowed remote workflows include:
  • bun check, bun test, and package-local Bun commands
  • bun test:e2e and Docker-backed Playwright workflows
  • bun sb:start, bun sb:reset, bun sb:up, and bun sb:typegen
Blocked defaults include privileged Docker containers, Docker prune-all style commands, absolute host mounts, sudo, and host-destructive filesystem operations.

Env And Secrets

Remote env is explicit. Local .env* files are never synced automatically. The setup command only prepares ignored app env files inside the runner checkout so local Supabase-backed apps and E2E workflows can use that runner’s own Supabase stack.
ttr box run --env DATABASE_URL=postgres://remote -- bun check
ttr box run --database-url-env DEVBOX_DATABASE_URL -- bun check
ttr box serve --database-url-env DEVBOX_DATABASE_URL
ttr box run --env-file .env.remote -- bun test:e2e
ttr box env set --lease <lease-id> API_TOKEN=value
ttr box env unset --lease <lease-id> API_TOKEN
Brokered env values are scoped to the run or lease, delivered only to the claimed runner, and redacted from command logs. For split-resource development, run Supabase or another database on one devbox, publish its reachable URL as DEVBOX_DATABASE_URL on the operator machine, and queue the app devbox with --database-url-env DEVBOX_DATABASE_URL. This lets a database-heavy runner and a Next.js runner share work while the current machine only brokers commands and opens previews.

Cache Policy

Runners use named cache volumes for Bun installs, Turbo, Playwright browsers, Supabase Docker state, package manager cache, and optional node_modules. Cache keys include the repo fingerprint, lockfile hash, runtime image digest, platform, command profile, Bun/Node versions, and cache schema version. Cleanup runs on runner startup, after runs, and during heartbeat maintenance. The runner evicts incompatible caches first, including legacy Bun install caches when the Bun version, lockfile hash, package profile, or cache schema changes. It then enforces cache budgets with least-recently-used eviction while keeping a small set of recent compatible caches to avoid thrashing. Operators can inspect and prune cache metadata:
ttr box cache list
ttr box cache doctor
ttr box cache prune

Access

Remote devbox API routes require a Tuturuuu CLI/app session and root workspace membership with workspace_members.type = 'MEMBER'. Guests and non-root workspace users cannot create runs, leases, previews, or runner tokens.

Local Executable Verification

Use an isolated CLI config when testing local apps/web so the production CLI session is not overwritten:
bun sb:status
docker exec supabase_db_tuturuuu psql -U postgres -d postgres -tAc "select to_regclass('private.devbox_leases'), exists (select 1 from supabase_migrations.schema_migrations where version = '20260603171600')"
TUTURUUU_CONFIG=/tmp/ttr-devbox-local-config.json bun ttr login --base-url http://localhost:7803
TUTURUUU_CONFIG=/tmp/ttr-devbox-local-config.json bun ttr box agent register --name local-devbox
TUTURUUU_CONFIG=/tmp/ttr-devbox-local-config.json bun ttr box agent start --token <runner-token>
TUTURUUU_CONFIG=/tmp/ttr-devbox-local-config.json bun ttr box run -- bun --version
The final command should print the remote command logs and exit with the remote command’s exit code. In local Supabase, private.devbox_runs.status should end as succeeded with exit_code = 0.

Production Readiness

The production API needs migration 20260603171600_create_private_devboxes.sql before any ttr box command can create leases or runs. If the CLI prints a schema-cache error such as private.devbox_leases not being found, first confirm the production migration workflow ran for the deployed SHA. If the table exists but PostgREST still returns the schema-cache error, refresh the schema cache from Supabase SQL Editor:
select to_regclass('private.devbox_leases');
select version from supabase_migrations.schema_migrations
where version = '20260603171600';
notify pgrst, 'reload schema';