Boat
Boat cloud sandboxes (full root Linux VMs) as a Bijection component: reactive state, retry-safe create, exec, files, preview URLs, coding agents.
git clone https://github.com/bijectionhq/boat.git
cd boat
git checkout 4dd9f60f8fbc561b8ff9486bded92bd24962c640Bijection repository access is required. Request access.
Description
Boat cloud sandboxes (full root Linux VMs) as a Bijection component: reactive state, retry-safe create, exec, files, preview URLs, coding agents.
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/boat. Based on ariana-dot-dev/boat-convex. License: MIT. Consult the repository for license terms, notices, and contributor attribution.
Compute runs with the external sandbox provider. Bijection connects application state and operations to that provider.
From the upstream publisher
Boat cloud sandboxes (full root Linux VMs) as a Convex component: reactive state, retry-safe create, exec, files, preview URLs, coding agents.
Explore the demo ↗Upstream overview
From the upstream package documentation.
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.
Use cases
From the upstream package documentation.
- Building AI coding agents that need a persistent workspace with real toolchains, where each user gets an isolated environment that stops when idle and resumes exactly where it left off
- Running background processes like dev servers or long builds inside a sandbox and streaming their output back through Convex queries
- Forking a configured sandbox as a template to spin up identical environments for new users without reprovisioning from scratch
- Giving end users browser-accessible preview URLs for ports running inside their sandbox, with optional token gating
- Running coding agents like Claude Code or Codex inside a sandboxed VM and following their work through the prompt and events API
How it works
From the upstream package documentation.
Install the package and register the component in convex/convex.config.ts using app.use(boat, { env: { BOAT_API_KEY: app.env.BOAT_API_KEY } }). The API key is bound at the component level and never surfaces to application code or the browser. Set the key with npx convex env set BOAT_API_KEY boat_....
Every sandbox is addressed by an { ownerId, key } pair. The ownerId must always be derived from ctx.auth inside a Convex function, not passed in from the client. The create and fork methods use Boat idempotency keys, so retries return the existing sandbox without billing a second one.
The Boat class exposes queries (get, list) that read cached sandbox state from a Convex table, enabling reactive UI with useQuery. Mutations that call the Boat API are performed through actions: create, stop, resume, fork, destroy, exec, spawn, readFile, writeFile, host, and prompt. Background processes are started with spawn and polled with commandStatus. Coding agent tasks are handed off with prompt and tracked through events and promptStatus.
For multi-tenant scenarios where users drive the sandboxes directly, pass noEnv: true to create to prevent the sandbox from inheriting account-level secrets, then supply only the specific env values that sandbox needs.
Upstream package FAQ
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.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Boat with Bijection.
Read: https://bijection.com/components/boat/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from ariana-dot-dev/boat-convex, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
Boat Convex Component
Give every user, job or agent in your Convex app its own Boat sandbox: a full root Ubuntu VM (Docker, every toolchain, public IPv4) that stops to a snapshot when idle and resumes exactly where it left off.
- Reactive. Sandbox state lives in a Convex table.
useQueryshowsprovisioning→ready→archivedlive; the component follows every transition itself. - Retry-safe. One sandbox per
(ownerId, key). A retriedcreateorforkreturns the same sandbox and never bills a second one (Boat idempotency keys). - Your key stays server-side.
BOAT_API_KEYis a component env var; it never reaches your app code or the browser. - Everything a coding product needs: exec, background processes, files, public preview URLs, stop/resume, disk-level fork, and coding agents (Claude Code, Codex, pi, OpenCode) running inside the sandbox.
Install
npm install @boatdev/convex
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import boat from "@boatdev/convex/convex.config.js";
const app = defineApp({ env: { BOAT_API_KEY: v.string() } });
app.use(boat, { env: { BOAT_API_KEY: app.env.BOAT_API_KEY } });
export default app;
npx convex env set BOAT_API_KEY boat_... # from the Boat dashboard, API keys tab
Use
// convex/workspace.ts
import { Boat } from "@boatdev/convex";
import { components } from "./_generated/api";
import { action, query } from "./_generated/server";
import { v } from "convex/values";
const boat = new Boat(components.boat);
async function owner(ctx: { auth: { getUserIdentity(): Promise<{ subject: string } | null> } }) {
const user = await ctx.auth.getUserIdentity();
if (!user) throw new Error("Not signed in");
return user.subject;
}
export const status = query({
args: {},
handler: async (ctx) => boat.get(ctx, { ownerId: await owner(ctx), key: "workspace" }),
});
export const start = action({
args: {},
handler: async (ctx) =>
boat.create(ctx, {
ownerId: await owner(ctx),
key: "workspace",
options: { type: "small", ttlSeconds: 1800, noEnv: true },
}),
});
export const run = action({
args: { command: v.string() },
handler: async (ctx, { command }) =>
boat.exec(ctx, { ownerId: await owner(ctx), key: "workspace", command }),
});
const sandbox = useQuery(api.workspace.status); // live: provisioning -> ready
Always derive ownerId from ctx.auth, never from client arguments. The component has no auth of its own.
API
Every call names a sandbox by { ownerId, key }.
| Method | What it does |
|---|---|
get, list | Reactive reads of the cached state (queries). |
create({ options }) | Create, or return the existing sandbox for this key. Options: name, type (small/default/large), ttlSeconds (auto-stop, null = never), env, noEnv, environment, setupScript, from (named snapshot). |
refresh | Pull the live state from Boat, e.g. after an auto-stop. |
stop({ force? }) | Snapshot the disk and stop billing. |
resume({ options? }) | Bring it back exactly as it was, optionally on another size. |
fork({ newKey }) | Copy the sandbox, disk and all, into a new one. Copies the latest snapshot, which every stop takes. |
destroy | Permanently delete the sandbox and its snapshots. |
exec({ command, cwd?, timeoutSeconds? }) | Run and wait (up to 600 s). |
spawn({ command }) → commandStatus({ processId }) | Background process (dev server, long build) and its logs. |
readFile, writeFile | utf8 or base64; paths under /home/user or /tmp. |
host({ port, public? }) | Public HTTPS URL for a port. Token-gated unless public: true. |
prompt({ prompt, provider }) → events, promptStatus | Hand a task to a coding agent inside the sandbox and follow its work. |
Multi-tenant safety
By default a sandbox inherits your Boat account's secrets and repos. For sandboxes your users drive, pass
noEnv: true and give each sandbox only what it needs through env.
Develop
npm install
npm test # offline, Boat API mocked
CONVEX_AGENT_MODE=anonymous npx convex init
npx convex env set BOAT_API_KEY boat_...
npx convex dev # local backend + the example app
npm run e2e # real sandboxes: create, exec, files, preview, stop, fork, resume, destroy
License
MIT