Boat Sandboxes

Boat Sandboxes gives every Convex user or agent a persistent Ubuntu VM with reactive state, exec, files, preview URLs, fork, and coding agent support.

Installation

npm install @boatdev/convex

About Boat Sandboxes

Boat Convex integrates full root Ubuntu VMs into Convex applications, giving each user, job, or agent its own isolated sandbox with persistent disk state, snapshotting, and resume. Sandbox lifecycle state is stored in a Convex table and exposed reactively via queries, so UI can track provisioning through ready to archived without polling. The component wraps the Boat API server-side, keeping credentials out of client code entirely.

Benefits

Use cases

how to give each user their own Linux sandbox in a Convex app

The Boat Sandboxes component lets you call boat.create with an ownerId derived from ctx.auth and a string key to provision a dedicated Ubuntu VM per user. The sandbox state is stored in a Convex table, so useQuery on a status query returns live provisioning and ready transitions without any polling logic.

how to run exec commands in a cloud VM from a Convex action

After provisioning a sandbox with boat.create, call boat.exec from a Convex action with the ownerId, key, and command string. The call blocks until the command completes, up to 600 seconds, and returns the output. For long-running processes use boat.spawn and poll with boat.commandStatus.

how to run coding agents like Claude Code inside a Convex backend

The Boat Sandboxes component exposes a prompt method that takes a prompt string and provider, hands the task to a coding agent running inside the sandbox, and returns an event stream you can follow with promptStatus. This lets you orchestrate Claude Code, Codex, or other supported agents from a Convex action without managing any agent infrastructure yourself.

how to snapshot and resume a VM for cost savings in a multi-tenant app

Call boat.stop to snapshot the sandbox disk and halt billing, then boat.resume to restore it exactly as it was, optionally on a different instance size. The ttlSeconds option on create enables automatic stop after an idle period. Fork lets you copy a stopped sandbox at the disk level into a new key, which is useful for template-based workspace provisioning.

Frequently asked questions

Is Boat Sandboxes safe to use in a multi-tenant app where users control the sandbox?

The Boat Sandboxes component supports multi-tenant isolation through the noEnv option on boat.create. By default a sandbox inherits your Boat account secrets and repository access, so for user-driven sandboxes you should pass noEnv: true and supply only the environment variables that specific sandbox needs via the env option. The component has no built-in auth, so always derive ownerId from ctx.auth on the server rather than accepting it as a client argument.

Does boat.create charge twice if a Convex action retries?

No. The Boat Sandboxes component uses Boat idempotency keys tied to the ownerId and key pair. A retried create or fork call returns the existing sandbox rather than provisioning a new one, so retries are safe and never result in duplicate billing.

How do I expose a port from a sandbox as a public HTTPS URL?

Call boat.host with the ownerId, key, and port number from a Convex action. By default the URL is token-gated. Pass public: true to make it accessible without a token. This is intended for preview URLs for dev servers or web apps running inside the sandbox.

What Ubuntu instance sizes are available and how do I control auto-stop?

The type option on boat.create accepts small, default, or large. The ttlSeconds option sets an inactivity auto-stop timer in seconds; pass null to disable auto-stop entirely. You can also call boat.stop manually to snapshot and stop at any time, and boat.resume to bring the sandbox back, optionally on a different size.

How do I set the BOAT_API_KEY for the component?

Run npx convex env set BOAT_API_KEY boat_... with your key from the Boat dashboard. In convex.config.ts, pass the env variable through to the component using app.use(boat, { env: { BOAT_API_KEY: app.env.BOAT_API_KEY } }). The key is scoped to the component and never exposed to your application code or the browser.

Links