Daytona
Sandbox lifecycle and execution
git clone https://github.com/bijectionhq/daytona.git
cd daytona
git checkout 78de84a52a9046fdf8fdd8df1f5646a32fd046e6
cd packages/convexBijection repository access is required. Request access.
Description
Sandbox lifecycle and execution
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
This package has a pinned source revision in Bijection’s workspace. 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/daytona. Based on daytona/integrations. License: Apache-2.0. 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
Convex component for Daytona — create sandboxes, run commands and code, and manage sandbox files from your Convex backend, with sandbox and execution state tracked in reactive Convex tables
Explore the demo ↗Demo video
Upstream overview
From the upstream package documentation.
The Daytona component for Convex lets you create and manage isolated Daytona sandboxes directly from Convex actions, execute shell commands and Python/other code inside them, and read/write files -- with every sandbox and execution recorded in reactive component tables. This eliminates the need to poll external APIs for execution status: your UI can subscribe to live query results that reflect sandbox state, exit codes, and output history as they change. The component talks to the Daytona REST API via plain fetch in Convex's default runtime, so no Node.js runtime or heavy SDK bundle is required.
Use cases
From the upstream package documentation.
- Building an AI coding agent that needs an isolated execution environment per user session, where the UI needs to show real-time status (running, completed, failed) without polling
- Implementing a code interpreter endpoint that creates a sandbox on demand, runs submitted Python or shell code, and returns structured output including exit codes
- Running long-duration ML training jobs using runBackground so the action returns immediately and a scheduler-driven poller streams logs and exit codes into queryable Convex tables
- Scoping sandboxes to individual users with the userKey option so each user's sandbox list and execution history is isolated and queryable per identity
- Serving live preview URLs for web processes running inside a sandbox, where the port is exposed and a signed URL is returned to the client
How it works
From the upstream package documentation.
After installing the package and registering the component in convex.config.ts via app.use(daytona), you set a DAYTONA_API_KEY environment variable on your Convex deployment. The Daytona client is instantiated once with components.daytona and used inside actions to call methods like createSandbox, run, runCode, writeFile, and getPreviewUrl. Sandboxes can be created from a named snapshot or a Docker image, with optional resource settings (cpu, memory, disk) applying only to image-based creation.
Every sandbox lifecycle event and command execution is written to internal component tables, which means listSandboxes, getSandbox, listExecutions, and getExecution are plain Convex queries that return live data without hitting the Daytona API. This is the core architectural benefit: agent and dashboard UIs drive their state from reactive queries rather than polling.
For commands that exceed the 10-minute Convex action timeout, runBackground starts the command in a sandbox session and returns an executionId immediately. A scheduler-driven poller then checks in periodically, streams logs into the execution row, and records the final exit code when the command finishes. Synchronous run and runCode calls default to a 540-second timeout and are capped at 570 seconds.
Authorization is handled in your own functions since components cannot access ctx.auth directly. The recommended pattern is to resolve a user identity in your action or query, then pass identity.subject as the userKey when creating or listing sandboxes, which scopes all bookkeeping to that user without any additional configuration.
Upstream package FAQ
Does the Daytona Convex component require the Node.js runtime?
No. The @daytona/convex component communicates with the Daytona REST API using plain fetch in Convex's default runtime. There is no need for 'use node' directives, and the component carries no heavy SDK dependencies, which keeps cold start times low.
How does the Daytona component store execution history in Convex?
Every call to run or runCode writes an execution record to a component-managed table with status transitions from running to completed or failed. Output is stored up to 64 KB per record, while the action return value carries up to 4 MB. These records are queryable via listExecutions and getExecution without making any Daytona API calls.
What are the timeout limits for running commands with the Daytona Convex component?
Convex actions time out after 10 minutes. The timeoutSeconds parameter on run and runCode defaults to 540 seconds and is capped at 570 seconds. For jobs that take longer, start a background process in the sandbox using nohup and poll its status with follow-up run calls.
How do I scope Daytona sandboxes to individual users in a Convex app?
Pass a userKey string to createSandbox, run, or runCode to associate resources with a specific user or agent. Because Convex components cannot access ctx.auth directly, authenticate callers in your own query or action function, resolve the user identity, and forward it as the userKey value when calling component methods.
What sandbox resource options does the Daytona Convex component support?
Sandboxes can be created from a named snapshot or a Docker image. When using an image, you can specify cpu, memory, and disk resources. Snapshot-based sandboxes use the resource configuration defined in the snapshot. You can also set autoStopInterval to pause idle sandboxes and autoDeleteInterval to control automatic deletion, which helps manage costs.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Daytona with Bijection.
Read: https://bijection.com/components/daytona/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from daytona/integrations, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
Run Daytona sandboxes from your Convex backend: create isolated sandboxes, execute shell commands and code, read/write files, and get live preview URLs — with sandbox and execution state tracked in reactive Convex tables your UI can subscribe to.
// convex/agent.ts
import { Daytona } from "@daytona/convex";
import { v } from "convex/values";
import { components } from "./_generated/api";
import { action } from "./_generated/server";
const daytona = new Daytona(components.daytona);
export const codeInterpreter = action({
args: { code: v.string() },
handler: async (ctx, args) => {
const { sandboxId } = await daytona.createSandbox(ctx, {
autoStopInterval: 15,
});
const { result, exitCode } = await daytona.runCode(ctx, {
sandboxId,
code: args.code,
language: "python",
});
return { result, exitCode };
},
});
Why a component instead of calling the Daytona API directly?
- Reactive state — every sandbox and execution is recorded in component tables. Drive agent UIs ("running…", exit codes, output history) from live Convex queries instead of polling.
- Isolated bookkeeping — the component keeps its tables in its own namespace; it can't touch your app's data, and your app manages it through one typed client.
- No SDK bundle — the component talks to the Daytona REST API with plain
fetchin Convex's default runtime: no"use node", no heavy dependencies, fast cold starts.
Installation
npm install @daytona/convex
Set your Daytona API key on your deployment (create one at app.daytona.io/dashboard/keys):
npx convex env set DAYTONA_API_KEY dtn_...
Then create or update convex/convex.config.ts in your app. Components can't read your app's env vars, so pass the key down to the component:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import daytona from "@daytona/convex/convex.config.js";
const app = defineApp({
env: { DAYTONA_API_KEY: v.string() },
});
app.use(daytona, {
env: { DAYTONA_API_KEY: app.env.DAYTONA_API_KEY },
});
export default app;
Passing it by reference means the component always sees the current value, so rotating the key doesn't need a redeploy.
Optional: DAYTONA_API_URL (self-hosted instances; defaults to https://app.daytona.io/api). Set it the same way. It isn't a secret, so it doesn't need passing down: the client forwards it with each call.
Usage
Instantiate the client with the installed component. Credentials resolve from your deployment's environment variables, or pass them explicitly:
import { Daytona } from "@daytona/convex";
import { components } from "./_generated/api";
const daytona = new Daytona(components.daytona);
// or: new Daytona(components.daytona, { apiKey: "dtn_..." })
runBackground is the exception: it always uses the key passed down to the component in convex.config.ts.
Sandbox lifecycle
export const create = action({
args: {},
handler: async (ctx) => {
return await daytona.createSandbox(ctx, {
snapshot: "my-snapshot", // optional — org default when omitted
autoStopInterval: 15, // pause after 15 idle minutes (fs preserved)
autoDeleteInterval: -1, // never auto-delete
userKey: "user_123", // scope sandboxes to a user/agent (see below)
});
// → { sandboxId: "…", state: "started" }
},
});
Sandboxes are created from a snapshot (yours, or the org default) or built from a Docker image — pass one or the other. cpu/memory/disk only apply to image-based creation; snapshots define their own resources:
await daytona.createSandbox(ctx, { image: "python:3.12", cpu: 2, memory: 4 });
Also available: startSandbox, stopSandbox, deleteSandbox, and refreshSandbox (re-syncs remote state into the component's tables; returns null if the sandbox no longer exists).
Run commands and code
const { executionId, exitCode, result } = await daytona.run(ctx, {
sandboxId,
command: "python train.py",
cwd: "/home/daytona/project",
timeoutSeconds: 300,
});
const py = await daytona.runCode(ctx, {
sandboxId,
code: "print(sum(range(10)))",
language: "python",
});
A stopped or archived sandbox is transparently restarted first (disable with autoStart: false). Every call records an execution row (running → completed/failed) with truncated output, so history and status are queryable.
For long-running commands, runBackground starts the command in a sandbox session and returns immediately — the launch action isn't held open for the command's duration (a lightweight scheduler-driven poller checks in periodically instead), and the command isn't bound to the 10-minute action ceiling:
const { executionId } = await daytona.runBackground(ctx, {
sandboxId,
command: "python train.py",
cwd: "/home/daytona/project",
});
// Watch it reactively: a scheduler-driven poller streams logs into the row
// while it runs and records the exit code when it finishes.
export const training = query({
// Component table IDs cross the boundary as plain strings.
args: { executionId: v.string() },
handler: async (ctx, args) => daytona.getExecution(ctx, args),
});
To let your backend react when the command finishes (instead of polling the row), pass an onComplete mutation — the component invokes it on every terminal state (completed, failed, or cancelled) with the outcome and an optional context you supply:
const { executionId } = await daytona.runBackground(ctx, {
sandboxId,
command: "cargo build",
onComplete: internal.build.buildFinished, // (ctx, { executionId, status, exitCode, result, error, context })
onCompleteContext: { appId },
minPollMs: 500, // first poll + backoff floor (default 1000, min 250)
maxPollMs: 5000, // backoff ceiling (default 10000, max 120000)
});
Cancel a running background execution with daytona.cancelExecution(ctx, { executionId }) — it stops the command (kills its session), marks the row cancelled, and fires the onComplete handler.
A running background command does not reset the sandbox's idle auto-stop timer — create sandboxes for long jobs with autoStopInterval: 0.
runBackground needs the key passed down as shown in Installation. Its poller runs from the scheduler, outside any of your calls, so it reads DAYTONA_API_KEY from the component's env on every poll instead of carrying it in scheduled function args.
Reactive state
These read the component's tables — no Daytona API call, and they update live:
export const sandboxes = query({
args: {},
handler: async (ctx) => daytona.listSandboxes(ctx, { userKey: "user_123" }),
});
export const executions = query({
args: { sandboxId: v.string() },
handler: async (ctx, args) => daytona.listExecutions(ctx, args),
});
Also: getSandbox, getExecution.
Files
await daytona.writeFile(ctx, { sandboxId, path: "/home/daytona/app.py", content });
const text = await daytona.readFile(ctx, { sandboxId, path: "/home/daytona/app.py" });
const entries = await daytona.listFiles(ctx, { sandboxId, path: "/home/daytona" });
await daytona.deleteFile(ctx, { sandboxId, path: "/home/daytona/tmp", recursive: true });
readFile/writeFile move content as UTF-8 strings — the right default for code, configs, and logs. For binary files (images, PDFs, archives), use the byte variants, which pass raw ArrayBuffers through untouched:
const png = await daytona.readFileBytes(ctx, { sandboxId, path: "/home/daytona/chart.png" });
await ctx.storage.store(new Blob([png], { type: "image/png" })); // e.g. into Convex file storage
await daytona.writeFileBytes(ctx, { sandboxId, path: "/home/daytona/data.zip", content: zipBuffer });
Either way, file content crosses the Convex function boundary, so keep individual files under Convex's 16 MiB argument/return limits.
Preview URLs
const { url } = await daytona.getPreviewUrl(ctx, { sandboxId, port: 3000 });
Signed URLs (default) embed a short-lived token; pass signed: false for a standard URL + token pair.
Authorization
Components can't see your app's ctx.auth — authenticate callers in your own functions and scope sandboxes with userKey:
export const mySandboxes = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await daytona.listSandboxes(ctx, { userKey: identity.subject });
},
});
Limits & long-running work
- Convex actions time out after 10 minutes, so synchronous
run/runCodecommands are always bounded below that ceiling:timeoutSecondsdefaults to 540 and is capped at 570. For longer jobs, userunBackground— it has no duration bound and holds no action open. - Sandbox and execution rows are kept as audit history. They never clean themselves up — prune them in batches with
daytona.purgeSandboxes(ctx, { olderThanMs })anddaytona.purgeExecutions(ctx, { olderThanMs })(each call returnshasMore; terminal rows only, remote sandboxes untouched).listSandboxesaccepts astatefilter (e.g."started"for a live count) andlistSandboxesPaginatedprovides cursor pagination. - Stored execution output is truncated (64 KB); the action's return value carries up to 4 MB.
- Sandboxes cost money while running: set
autoStopInterval, and delete sandboxes you're done with.refreshSandboxreconciles records whose remote sandbox was removed out-of-band.
See example/convex/example.ts for a complete example app.
Found a bug? Feature request? File it here.