Migrate from Daytona to Blaxel
This guide covers migrating cloud sandbox code from Daytona (@daytonaio/sdk / daytona_sdk) to Blaxel (@blaxel/core / blaxel).
Package replacement
| Language | Daytona | Blaxel |
|---|---|---|
| TypeScript | @daytonaio/sdk |
@blaxel/core |
| Python | daytona_sdk or daytona |
blaxel |
# TypeScript
npm uninstall @daytonaio/sdk
npm install @blaxel/core
# Python
pip uninstall daytona_sdk daytona
pip install blaxel
Authentication
| Daytona | Blaxel |
|---|---|
DAYTONA_API_KEY env var |
BL_WORKSPACE + BL_API_KEY env vars |
Passed to new Daytona({ apiKey }) / Daytona(api_key=...) |
Auto-detected by SDK (CLI login, .env, env vars, or config file) |
Blaxel authenticates automatically when logged in via CLI (bl login YOUR-WORKSPACE). No need to pass credentials to constructors.
API mapping
Imports
TypeScript:
// BEFORE (Daytona)
import { Daytona, Sandbox as DaytonaSandbox } from '@daytonaio/sdk'
// AFTER (Blaxel)
import { SandboxInstance } from '@blaxel/core'
Python:
# BEFORE (Daytona)
from daytona_sdk import Daytona, CreateSandboxParams
# AFTER (Blaxel)
from blaxel.core import SandboxInstance
Sandbox creation
Daytona requires instantiating a client first, then calling create(). Blaxel uses static methods directly on SandboxInstance.
TypeScript:
// BEFORE (Daytona)
const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY })
const sandbox = await daytona.create({
language: 'python',
image: 'debian:slim',
resources: { memory: 2, cpu: 1 }, // memory in GB
envVars: { NODE_ENV: 'production' },
labels: { project: 'my-app' },
target: 'us', // region: 'us', 'eu', or 'asia'
autoStopInterval: 15, // minutes
})
// AFTER (Blaxel)
const sandbox = await SandboxInstance.createIfNotExists({
name: 'my-sandbox',
image: 'blaxel/base-image:latest',
region: 'us-pdx-1',
memory: 2048, // memory in MB
ports: [{ target: 3000, protocol: 'HTTP' }],
envs: [
{ name: 'NODE_ENV', value: 'production' },
],
labels: { project: 'my-app' },
})
Python:
# BEFORE (Daytona)
daytona = Daytona(api_key=os.environ["DAYTONA_API_KEY"])
sandbox = await daytona.create(CreateSandboxParams(
language="python",
image="debian:slim",
resources=Resources(cpu=1, memory=2),
env_vars={"NODE_ENV": "production"},
labels={"project": "my-app"},
target="us",
auto_stop_interval=15,
))
# AFTER (Blaxel)
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-pdx-1",
"memory": 2048,
"ports": [{"target": 3000, "protocol": "HTTP"}],
"envs": [
{"name": "NODE_ENV", "value": "production"},
],
"labels": {"project": "my-app"},
})
Parameter mapping
| Parameter | Daytona | Blaxel |
|---|---|---|
| Image | image: 'debian:slim' |
image: 'blaxel/base-image:latest' |
| Memory | resources: { memory: 2 } (GB) |
memory: 2048 (MB) |
| CPU | resources: { cpu: 1 } |
Not needed (auto-allocated: CPU cores = memory / 2048) |
| Region | target: 'us' / 'eu' / 'asia' |
region: 'us-pdx-1' |
| Environment vars | envVars: { KEY: 'val' } (flat object) |
envs: [{ name: 'KEY', value: 'val' }] (array of objects) |
| Secrets | N/A | envs: [{ name: 'KEY', value: 'val', secret: true }] |
| Labels | labels: { k: 'v' } |
labels: { k: 'v' } |
| Auto-stop | autoStopInterval: 15 (minutes) |
Automatic (~5s inactivity, resumes in <25ms). For idle-based deletion: lifecycle: { expirationPolicies: [{ type: 'ttl-idle', value: '7d', action: 'delete' }] } |
| Auto-archive | autoArchiveInterval: 10080 (minutes) |
Not needed (state preserved in standby) |
| Auto-delete | autoDeleteInterval: 43200 (minutes) |
lifecycle: { expirationPolicies: [{ type: 'ttl', value: '30d', action: 'delete' }] } |
| Ephemeral | ephemeral: true |
Use sandbox.delete() when done |
| Name | name: 'my-sandbox' |
name: 'my-sandbox' |
| Ports | N/A (all ports accessible via preview URL) | ports: [{ target: 3000, protocol: 'HTTP' }] (declared at creation) |
| TTL | N/A | ttl: '24h' |
| GPU | resources: { gpu: 1 } |
N/A (Blaxel sandboxes are CPU-only) |
| Disk | resources: { disk: 10 } (GiB) |
N/A (Blaxel uses ~50% memory for tmpfs; use volumes for extra storage) |
| Network control | networkBlockAll: true, networkAllowList: '10.0.0.0/8' |
network: { vpcName: 'my-vpc', egressGatewayName: 'my-gw' } (VPC + egress gateway). No CIDR rules |
| Public access | public: true (all ports accessible) |
Per-port via previews.createIfNotExists({ spec: { public: true } }) |
| User | user: 'root' |
N/A (commands run as default user) |
| PTY / Terminal | sandbox.process.createPty({ cols, rows, onData }) |
N/A (no interactive terminal support) |
| SSH access | sandbox.createSshAccess(expiresInMinutes) |
N/A (no SSH access) |
| LSP | sandbox.createLspServer(languageId, path) |
N/A (no LSP API; Blaxel has sandbox.codegen.reranking() for code search) |
| Computer Use | sandbox.computerUse.* (mouse, keyboard, screenshot) |
N/A (no desktop automation) |
| Metrics | N/A | N/A |
Custom sandbox / templates
TypeScript:
// BEFORE (Daytona) - uses snapshots
const sandbox = await daytona.create({
snapshot: 'my-template',
autoStopInterval: 1,
})
// AFTER (Blaxel) - uses custom images deployed via `bl deploy`
const sandbox = await SandboxInstance.createIfNotExists({
name: 'my-sandbox',
image: 'my-custom-template-image',
region: 'us-pdx-1',
memory: 2048,
})
Python:
# BEFORE (Daytona) - uses snapshots
sandbox = await daytona.create(CreateSandboxParams(
snapshot="my-template",
auto_stop_interval=1,
))
# AFTER (Blaxel) - uses custom images deployed via `bl deploy`
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "my-custom-template-image",
"region": "us-pdx-1",
"memory": 2048,
})
Image builder mapping
Daytona has a declarative Image builder with chainable methods. Blaxel uses standard Dockerfiles deployed via bl deploy.
| Daytona Image Builder | Dockerfile Equivalent |
|---|---|
Image.base('ubuntu:22.04') |
FROM ubuntu:22.04 |
Image.debianSlim('3.11') |
FROM python:3.11-slim |
Image.fromDockerfile(path) |
Use that Dockerfile directly with bl deploy |
.pipInstall(['numpy', 'pandas']) |
RUN pip install numpy pandas |
.pipInstallFromRequirements('requirements.txt') |
COPY requirements.txt . && RUN pip install -r requirements.txt |
.pipInstallFromPyproject('pyproject.toml') |
COPY pyproject.toml . && RUN pip install . |
.runCommands(['apt-get update', 'apt-get install -y curl']) |
RUN apt-get update && apt-get install -y curl |
.addLocalFile('app.py', '/app/app.py') |
COPY app.py /app/app.py |
.addLocalDir('./src', '/app/src') |
COPY ./src /app/src |
.env({ KEY: 'val' }) |
ENV KEY=val |
.workdir('/app') |
WORKDIR /app |
.entrypoint(['python3', 'app.py']) |
ENTRYPOINT ["python3", "app.py"] |
.cmd(['--port', '8080']) |
CMD ["--port", "8080"] |
.dockerfileCommands('RUN echo hello') |
RUN echo hello (add directly to Dockerfile) |
Deploy custom images with:
bl deploy -t sandbox --name my-template --dir ./path/to/Dockerfile/dir
Command execution
TypeScript:
// BEFORE (Daytona)
const result = await sandbox.process.executeCommand('echo hello')
const result = await sandbox.process.executeCommand(command, workingDir, envVars)
// Stateless code execution:
const result = await sandbox.process.codeRun('print("hello")')
// Stateful code execution (Python only):
const context = await sandbox.process.createContext()
await sandbox.process.runCode('x = 42', context)
await sandbox.process.runCode('print(x)', context) // remembers x
// AFTER (Blaxel)
const result = await sandbox.process.exec({
command: 'echo hello',
waitForCompletion: true,
})
const result = await sandbox.process.exec({
command: command,
workingDir: '/app',
env: { NODE_ENV: 'production' },
waitForCompletion: true,
})
// For code execution, use the command directly:
const result = await sandbox.process.exec({
command: 'python3 -c "print(42)"',
waitForCompletion: true,
})
Python:
# BEFORE (Daytona)
result = await sandbox.process.execute_command("echo hello")
result = await sandbox.process.execute_command(command, cwd="/app", env={"KEY": "val"})
result = await sandbox.process.code_run('print("hello")')
# Stateful code execution:
context = await sandbox.process.create_context()
await sandbox.process.run_code("x = 42", context)
await sandbox.process.run_code("print(x)", context)
# AFTER (Blaxel)
result = await sandbox.process.exec({
"command": "echo hello",
"wait_for_completion": True,
})
result = await sandbox.process.exec({
"command": command,
"working_dir": "/app",
"env": {"KEY": "val"},
"wait_for_completion": True,
})
# For code execution, use the command directly:
result = await sandbox.process.exec({
"command": 'python3 -c "print(42)"',
"wait_for_completion": True,
})
Long-running processes (dev servers)
TypeScript:
// BEFORE (Daytona) - no native streaming, must redirect to temp file and poll
const bgCommand = `cd /app && npm run dev > /tmp/output.log 2>&1 & echo $! > /tmp/pid.txt`
await sandbox.process.executeCommand(bgCommand, undefined, envVars)
// ... poll /tmp/output.log ...
// Or use interactive sessions:
await sandbox.process.createSession('dev')
await sandbox.process.executeSessionCommand('dev', {
command: 'npm run dev',
runAsync: true,
})
// AFTER (Blaxel) - native background mode with port waiting
const devServer = await sandbox.process.exec({
name: 'dev-server',
command: 'npm run dev -- --host 0.0.0.0',
workingDir: '/app',
waitForPorts: [3000], // returns once port 3000 is open
})
Python:
# BEFORE (Daytona) - no native streaming
bg_command = "cd /app && npm run dev > /tmp/output.log 2>&1 & echo $! > /tmp/pid.txt"
await sandbox.process.execute_command(bg_command, env=env_vars)
# Or use interactive sessions:
await sandbox.process.create_session("dev")
await sandbox.process.execute_session_command("dev", {
"command": "npm run dev",
"run_async": True,
})
# AFTER (Blaxel) - native background mode with port waiting
dev_server = await sandbox.process.exec({
"name": "dev-server",
"command": "npm run dev -- --host 0.0.0.0",
"working_dir": "/app",
"wait_for_ports": [3000],
})
Log streaming / Process output
Daytona uses session-based command logs with getSessionCommandLogs() and streaming via onStdout/onStderr callbacks. Blaxel provides callbacks on process.exec() plus dedicated log streaming methods.
TypeScript:
// BEFORE (Daytona) - session-based log retrieval
await sandbox.process.createSession('build')
await sandbox.process.executeSessionCommand('build', { command: 'npm run build' })
const logs = await sandbox.process.getSessionCommandLogs('build', commandId)
// Streaming with callbacks:
await sandbox.process.executeCommand('npm run build', undefined, undefined, {
onStdout: (data) => console.log('stdout:', data),
onStderr: (data) => console.error('stderr:', data),
})
// AFTER (Blaxel) - callbacks on exec + dedicated log methods
const result = await sandbox.process.exec({
name: 'build',
command: 'npm run build',
workingDir: '/app',
waitForCompletion: true,
onLog: (log) => console.log(log),
onStdout: (data) => console.log('stdout:', data),
onStderr: (data) => console.error('stderr:', data),
})
// Stream logs from a running process:
const handle = sandbox.process.streamLogs('build', {
onLog: (log) => console.log(log),
onStdout: (data) => console.log('stdout:', data),
onStderr: (data) => console.error('stderr:', data),
})
// Wait for the process to finish before closing the stream:
await handle.wait()
// Retrieve logs after completion:
const logs = await sandbox.process.logs('build') // all logs
const stdout = await sandbox.process.logs('build', 'stdout') // stdout only
const stderr = await sandbox.process.logs('build', 'stderr') // stderr only
Python:
# BEFORE (Daytona) - session-based log retrieval
await sandbox.process.create_session("build")
await sandbox.process.execute_session_command("build", {"command": "npm run build"})
logs = await sandbox.process.get_session_command_logs("build", command_id)
# AFTER (Blaxel) - callbacks on exec + dedicated log methods
result = await sandbox.process.exec({
"name": "build",
"command": "npm run build",
"working_dir": "/app",
"wait_for_completion": True,
"on_log": lambda log: print(log),
"on_stdout": lambda data: print("stdout:", data),
"on_stderr": lambda data: print("stderr:", data),
})
# Stream logs from a running process:
handle = await sandbox.process.stream_logs("build", {
"on_log": lambda log: print(log),
})
# Wait for the process to finish before closing the stream:
await handle.wait()
# Retrieve logs after completion:
logs = await sandbox.process.logs("build") # all logs
stdout = await sandbox.process.logs("build", "stdout") # stdout only
Filesystem operations
| Operation | Daytona | Blaxel |
|---|---|---|
| Write file | sandbox.fs.uploadFile(Buffer.from(content), path) / upload_file(bytes, path) |
sandbox.fs.write(path, content) |
| Read file | sandbox.fs.downloadFile(path) / download_file(path) |
sandbox.fs.read(path) |
| List files | sandbox.fs.listFiles(path) / list_files(path) |
sandbox.fs.ls(path) |
| Write tree | N/A | sandbox.fs.writeTree(files, basePath) / write_tree(files, base) |
| Create dir | N/A (use executeCommand('mkdir -p ...')) |
sandbox.fs.mkdir(path) |
| Search files (content) | sandbox.fs.findFiles(path, pattern) (content grep) |
sandbox.fs.grep(pattern, path) |
| Search files (glob) | sandbox.fs.searchFiles(path, pattern) (glob) |
sandbox.fs.find(path, { patterns: ['*.ts'] }) |
| Fuzzy search | N/A | sandbox.fs.search(query, path) |
| Delete file | sandbox.fs.deleteFile(path, recursive?) |
sandbox.fs.rm(path) / sandbox.fs.rm(path, { recursive: true }) |
| Move file | sandbox.fs.moveFiles(source, dest) |
No native move — use sandbox.fs.cp(src, dest) + sandbox.fs.rm(src) or process.exec({ command: 'mv ...' }) |
| Copy file | N/A | sandbox.fs.cp(src, dest) |
| Replace in files | sandbox.fs.replaceInFiles(files, pattern, newValue) |
No equivalent — use process.exec({ command: "sed -i 's/old/new/g' file" }) |
| File details | sandbox.fs.getFileDetails(path) → size, mode, permissions, owner, group, modifiedTime |
No equivalent — use process.exec({ command: 'stat file' }) |
| Set permissions | sandbox.fs.setFilePermissions(path, { mode, owner, group }) |
No equivalent — use process.exec({ command: 'chmod/chown ...' }) |
| Batch upload | sandbox.fs.uploadFiles([{ path, content }]) |
sandbox.fs.writeTree(files, basePath) |
| Watch files | N/A | sandbox.fs.watch(path, callback, { withContent?, ignore? }) — returns handle with close() |
TypeScript:
// BEFORE (Daytona)
await sandbox.fs.uploadFile(Buffer.from('hello'), '/app/file.txt')
const blob = await sandbox.fs.downloadFile('/app/file.txt')
const content = blob.toString()
const files = await sandbox.fs.listFiles('/app')
// AFTER (Blaxel)
await sandbox.fs.write('/app/file.txt', 'hello')
const content = await sandbox.fs.read('/app/file.txt')
const { subdirectories, files } = await sandbox.fs.ls('/app')
// Blaxel also supports writing multiple files at once:
await sandbox.fs.writeTree([
{ path: 'src/index.ts', content: 'console.log("hello")' },
{ path: 'package.json', content: '{"name":"app"}' },
], '/app')
// Delete files:
await sandbox.fs.rm('/app/old-file.txt')
await sandbox.fs.rm('/app/old-dir', { recursive: true })
// Copy files:
await sandbox.fs.cp('/app/src/template.ts', '/app/src/copy.ts')
// Watch for file changes:
const handle = await sandbox.fs.watch('/app/src', (event) => {
console.log('Changed:', event.path, event.type)
}, { withContent: true, ignore: ['node_modules'] })
// Later: handle.close()
Python:
# BEFORE (Daytona)
await sandbox.fs.upload_file(content.encode(), "/app/file.txt")
blob = await sandbox.fs.download_file("/app/file.txt")
content = blob.decode()
files = await sandbox.fs.list_files("/app")
# AFTER (Blaxel)
await sandbox.fs.write("/app/file.txt", "hello")
content = await sandbox.fs.read("/app/file.txt")
listing = await sandbox.fs.ls("/app")
# Write multiple files:
await sandbox.fs.write_tree([
{"path": "src/index.ts", "content": 'console.log("hello")'},
{"path": "package.json", "content": '{"name":"app"}'},
], "/app")
# Delete files:
await sandbox.fs.rm("/app/old-file.txt")
await sandbox.fs.rm("/app/old-dir", {"recursive": True})
# Copy files:
await sandbox.fs.cp("/app/src/template.ts", "/app/src/copy.ts")
# Watch for file changes:
handle = await sandbox.fs.watch("/app/src", lambda event: print(f"Changed: {event}"),
{"with_content": True, "ignore": ["node_modules"]})
# Later: handle.close()
Preview URLs
Daytona provides getPreviewLink(port) for standard access, plus signed URLs with expiration. Blaxel has dedicated preview URL management with public/private access and token-based auth.
TypeScript:
// BEFORE (Daytona)
const link = sandbox.getPreviewLink(3000)
// Signed URL with expiration:
const signed = await sandbox.getSignedPreviewUrl(3000, 3600) // 1 hour
await sandbox.expireSignedPreviewUrl(3000, signed.token)
// AFTER (Blaxel) - dedicated preview URL with public/private access control
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: 'app-preview' },
spec: { port: 3000, public: true },
})
const url = preview.spec?.url // https://xxxx.preview.bl.run
// Private preview with token-based access:
const privatePreview = await sandbox.previews.createIfNotExists({
metadata: { name: 'private-preview' },
spec: { port: 3000, public: false },
})
const token = await privatePreview.tokens.create(
new Date(Date.now() + 3600 * 1000) // expires in 1 hour
)
// Access: https://xxxx.preview.bl.run?token=<token>
// Revoke:
await privatePreview.tokens.delete(token.id)
Python:
# BEFORE (Daytona)
link = sandbox.get_preview_link(3000)
signed = await sandbox.get_signed_preview_url(3000, 3600)
await sandbox.expire_signed_preview_url(3000, signed.token)
# AFTER (Blaxel)
preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "app-preview"},
"spec": {"port": 3000, "public": True},
})
url = preview.spec.url # https://xxxx.preview.bl.run
# Private preview with token:
private_preview = await sandbox.previews.create_if_not_exists({
"metadata": {"name": "private-preview"},
"spec": {"port": 3000, "public": False},
})
token = await private_preview.tokens.create(expires_at)
# Access: url + ?token=<token>
# Revoke:
await private_preview.tokens.delete(token.id)
Sandbox lifecycle
| Operation | Daytona | Blaxel |
|---|---|---|
| Delete | sandbox.delete() / daytona.delete(sandbox) |
sandbox.delete() |
| Stop | sandbox.stop() |
Automatic (scales to zero after ~5s) |
| Start / Resume | sandbox.start() |
Automatic (resumes in <25ms on next API call) |
| Archive | sandbox.archive() |
Not needed (state preserved) |
| Reconnect | daytona.get(sandboxId) / daytona.findOne({ labels }) |
SandboxInstance.get('my-sandbox') |
| Resize | sandbox.resize({ cpu, memory, disk }) + waitForResizeComplete() |
N/A — create new sandbox with different config |
| Recover | sandbox.recover() |
N/A — sandboxes auto-resume from standby |
| List sandboxes | daytona.list(labels?, page?, limit?) → paginated |
SandboxInstance.list() — returns all (client-side filtering via labels) |
| Exit code | result.exitCode on execute response |
result.exitCode on process response |
| Wait for start | sandbox.waitUntilStarted(timeout) |
Not needed — auto-resumes in <25ms |
| Wait for stop | sandbox.waitUntilStopped(timeout) |
Not needed |
| Wait for process | Poll session command logs | sandbox.process.wait('name', { maxWait, interval }) |
| Stop process | Kill via session | sandbox.process.stop('name') |
| List processes | sandbox.process.listSessions() |
sandbox.process.list() |
| Get process | sandbox.process.getSession(id) |
sandbox.process.get('name') |
| Update TTL | sandbox.setAutostopInterval(mins) / setAutoArchiveInterval() / setAutoDeleteInterval() |
SandboxInstance.updateTtl('name', '48h') or SandboxInstance.updateLifecycle('name', { expirationPolicies: [...] }) |
| Refresh activity | sandbox.refreshActivity() |
Not needed (auto-detected on API calls) |
| Labels (read) | sandbox.labels |
sandbox.metadata.labels |
| Labels (write) | sandbox.setLabels({ key: 'val' }) |
SandboxInstance.updateMetadata('name', { labels: { key: 'val' } }) |
| Sandbox info | sandbox.id, sandbox.state, sandbox.labels |
SandboxInstance.get('name') → sandbox.metadata, sandbox.status, sandbox.spec |
TypeScript:
// BEFORE (Daytona) - manual lifecycle management
await sandbox.stop()
// ... later ...
await sandbox.start()
// AFTER (Blaxel) - automatic, just reconnect by name
const sandbox = await SandboxInstance.get('my-sandbox')
// Sandbox resumes automatically if it was idle
Python:
# BEFORE (Daytona) - manual lifecycle management
await sandbox.stop()
# ... later ...
await sandbox.start()
# AFTER (Blaxel) - automatic, just reconnect by name
sandbox = await SandboxInstance.get("my-sandbox")
# Sandbox resumes automatically if it was idle
Code Interpreter
Daytona has sandbox.codeInterpreter for stateful Python execution. Blaxel provides a dedicated CodeInterpreter class that extends SandboxInstance with Jupyter kernel support for multiple languages.
TypeScript:
// BEFORE (Daytona)
import { Daytona } from '@daytonaio/sdk'
const daytona = new Daytona()
const sandbox = await daytona.create()
const ctx = await sandbox.codeInterpreter.createContext()
const result = await sandbox.codeInterpreter.runCode('x = 42', { context: ctx })
const result2 = await sandbox.codeInterpreter.runCode('print(x)', { context: ctx })
await sandbox.codeInterpreter.deleteContext(ctx)
// AFTER (Blaxel)
import { CodeInterpreter } from '@blaxel/core'
const interpreter = await CodeInterpreter.createIfNotExists({
name: 'my-interpreter',
region: 'us-pdx-1',
memory: 2048,
})
const ctx = await interpreter.createCodeContext({ language: 'python', name: 'ctx-1' })
const result = await interpreter.runCode({ code: 'x = 42', context: ctx })
const result2 = await interpreter.runCode({ code: 'print(x)', context: ctx })
Python:
# BEFORE (Daytona)
from daytona_sdk import Daytona
daytona = Daytona()
sandbox = await daytona.create()
ctx = await sandbox.code_interpreter.create_context()
result = await sandbox.code_interpreter.run_code("x = 42", context=ctx)
result2 = await sandbox.code_interpreter.run_code("print(x)", context=ctx)
await sandbox.code_interpreter.delete_context(ctx)
# AFTER (Blaxel)
from blaxel.core import CodeInterpreter
interpreter = await CodeInterpreter.create_if_not_exists({
"name": "my-interpreter",
"region": "us-pdx-1",
"memory": 2048,
})
ctx = await interpreter.create_code_context({"language": "python", "name": "ctx-1"})
result = await interpreter.run_code({"code": "x = 42", "context": ctx})
result2 = await interpreter.run_code({"code": "print(x)", "context": ctx})
Note: Daytona code interpreter is Python-only. Blaxel
CodeInterpreteruses a Jupyter kernel and supports multiple languages (Python, JavaScript, TypeScript, etc.). The default image isblaxel/jupyter-server.
Git integration
Daytona provides a full sandbox.git.* API. Blaxel has no dedicated git API — use process.exec() with git commands instead.
TypeScript:
// BEFORE (Daytona)
await sandbox.git.clone('https://github.com/user/repo.git', '/app')
const status = await sandbox.git.status('/app')
const branches = await sandbox.git.branches('/app')
await sandbox.git.add('/app', ['src/index.ts'])
await sandbox.git.commit('/app', 'feat: add feature')
await sandbox.git.push('/app')
// AFTER (Blaxel) - use process.exec with git commands
await sandbox.process.exec({
command: 'git clone https://github.com/user/repo.git /app',
waitForCompletion: true,
})
const status = await sandbox.process.exec({
command: 'git status --porcelain',
workingDir: '/app',
waitForCompletion: true,
})
await sandbox.process.exec({
command: 'git add src/index.ts && git commit -m "feat: add feature" && git push',
workingDir: '/app',
waitForCompletion: true,
})
Python:
# BEFORE (Daytona)
await sandbox.git.clone("https://github.com/user/repo.git", "/app")
status = await sandbox.git.status("/app")
await sandbox.git.add("/app", ["src/index.ts"])
await sandbox.git.commit("/app", "feat: add feature")
await sandbox.git.push("/app")
# AFTER (Blaxel) - use process.exec with git commands
await sandbox.process.exec({
"command": "git clone https://github.com/user/repo.git /app",
"wait_for_completion": True,
})
status = await sandbox.process.exec({
"command": "git status --porcelain",
"working_dir": "/app",
"wait_for_completion": True,
})
await sandbox.process.exec({
"command": 'git add src/index.ts && git commit -m "feat: add feature" && git push',
"working_dir": "/app",
"wait_for_completion": True,
})
Note: Git must be installed in the sandbox image. Use
process.exec()for any git operation:clone,status,branch,add,commit,push,pull, etc.
MCP integration
Daytona exposes MCP server integration for tools like Claude Desktop and Cursor. Blaxel sandboxes have a built-in MCP server accessible via SDK helpers.
TypeScript:
// BEFORE (Daytona) - MCP server via dashboard configuration
// Configure MCP in Claude Desktop / Cursor pointing to Daytona
// AFTER (Blaxel) - built-in MCP server on every sandbox
// Every sandbox exposes MCP at: https://<SANDBOX_URL>/mcp
// For framework integrations (Vercel AI SDK, LangChain, etc.):
import { blTools } from '@blaxel/core'
const tools = await blTools(['sandbox/my-sandbox'])
// Pass tools to your AI framework
Python:
# BEFORE (Daytona) - MCP via dashboard
# Configure MCP in Claude Desktop / Cursor pointing to Daytona
# AFTER (Blaxel) - built-in MCP server
# Every sandbox exposes MCP at: https://<SANDBOX_URL>/mcp
# For framework integrations:
from blaxel.core import bl_tools
tools = await bl_tools(["sandbox/my-sandbox"])
# Pass tools to your AI framework
Volumes
Daytona has daytona.volume.* for FUSE-based S3-compatible volumes. Blaxel provides VolumeInstance for persistent block storage.
TypeScript:
// BEFORE (Daytona)
import { Daytona } from '@daytonaio/sdk'
const daytona = new Daytona()
const volume = await daytona.volume.create({
name: 'my-data',
size: 10, // GiB
})
const sandbox = await daytona.create({ volumes: [{ volumeId: volume.id, mountPath: '/data' }] })
const volumes = await daytona.volume.list()
await daytona.volume.delete(volume.id)
// AFTER (Blaxel)
import { SandboxInstance, VolumeInstance } from '@blaxel/core'
const volume = await VolumeInstance.createIfNotExists({
name: 'my-data',
})
const sandbox = await SandboxInstance.createIfNotExists({
name: 'my-sandbox',
image: 'blaxel/base-image:latest',
region: 'us-pdx-1',
memory: 2048,
volumes: [{ name: 'my-data', mountPath: '/data' }],
})
const volumes = await VolumeInstance.list()
await volume.delete()
Python:
# BEFORE (Daytona)
from daytona_sdk import Daytona
daytona = Daytona()
volume = await daytona.volume.create(name="my-data", size=10)
sandbox = await daytona.create(volumes=[{"volume_id": volume.id, "mount_path": "/data"}])
volumes = await daytona.volume.list()
await daytona.volume.delete(volume.id)
# AFTER (Blaxel)
from blaxel.core import SandboxInstance, VolumeInstance
volume = await VolumeInstance.create_if_not_exists({"name": "my-data"})
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/base-image:latest",
"region": "us-pdx-1",
"memory": 2048,
"volumes": [{"name": "my-data", "mount_path": "/data"}],
})
volumes = await VolumeInstance.list()
await volume.delete()
Note: Daytona volumes are FUSE-based S3-compatible with concurrent multi-sandbox mounting and subpath support. Blaxel volumes are persistent block storage. Daytona allows configuring volume size; Blaxel volumes do not have a size parameter.
Snapshots
Daytona has explicit snapshot management (daytona.snapshot.create/get/list/delete/activate). Blaxel auto-preserves state through scale-to-zero — no explicit snapshot API needed.
// BEFORE (Daytona)
const snapshot = await daytona.snapshot.create({ sandboxId: sandbox.id, name: 'checkpoint-1' })
const restored = await daytona.create({ snapshot: 'checkpoint-1' })
const snapshots = await daytona.snapshot.list()
await daytona.snapshot.delete(snapshot.id)
// AFTER (Blaxel)
// State (memory + filesystem) is automatically preserved through auto-pause/resume.
// No explicit snapshot API needed.
// For sharing pre-built state across sandboxes, use volume templates:
const sandbox = await SandboxInstance.createIfNotExists({
name: 'my-sandbox',
image: 'my-pre-built-template', // deployed via `bl deploy`
volumes: [{ name: 'shared-state', mountPath: '/data' }],
})
Note: Daytona snapshots auto-deactivate after 2 weeks of inactivity. Blaxel state persists indefinitely until the sandbox is deleted or its TTL expires.
Key differences
- No client instantiation: Blaxel SDK uses static methods on
SandboxInstance; no need to create aDaytona()client - Memory units: Daytona uses GB, Blaxel uses MB (e.g.,
2GB in Daytona =2048MB in Blaxel) - Ports declared at creation: Blaxel requires ports at sandbox creation time; they cannot be added later. Ports 80, 443, 8080 are reserved
- Region format: Daytona uses
'us'/'eu'/'asia'; Blaxel uses specific region IDs like'us-pdx-1' - Auto scale-to-zero: Both support auto scale-to-zero. Blaxel sandboxes auto-pause after ~5s of inactivity and resume in <25ms. No manual stop/start needed. Use
lifecycle: { expirationPolicies: [{ type: 'ttl-idle', value: '7d', action: 'delete' }] }for idle-based deletion - Preview URLs: Blaxel has native preview URL support with public/private access control, token-based auth, and token revocation. Daytona's
getPreviewLink()/ signed URLs map to Blaxel'spreviews.createIfNotExists()+tokens.create() - Named sandboxes: Blaxel uses human-readable names (not opaque IDs). Supports
createIfNotExists/create_if_not_existsfor idempotent creation - Environment variables: Daytona uses a flat object (
envVars: { KEY: 'val' }/env_vars={"KEY": "val"}). Blaxel uses an array of objects at creation (envs: [{ name: 'KEY', value: 'val' }]) and a flat object per-command (env: { KEY: 'val' }) - Dev server binding: Dev servers must bind to
0.0.0.0(notlocalhost) for preview URLs to work in Blaxel - ~50% memory reserved for tmpfs: Blaxel uses half of sandbox memory for the in-memory filesystem. Use volumes for extra storage
- Async Python: Blaxel Python SDK is async-first (all calls use
await); Daytona Python SDK also supports async - No GPU support: Blaxel sandboxes are CPU-only. Daytona supports
resources: { gpu: 1 } - No configurable disk: Blaxel uses tmpfs (~50% of memory). Use volumes for extra storage. Daytona allows
resources: { disk: 10 }(GiB) with hot resize - No git API: Blaxel has no dedicated git methods. Use
process.exec({ command: 'git ...' })instead of Daytona'ssandbox.git.* - No LSP support: Blaxel has no Language Server Protocol API. Daytona offers
sandbox.createLspServer()with completions and symbol search. Blaxel hassandbox.codegen.reranking()for code search - No desktop automation: Blaxel has no computer use API. Daytona offers
sandbox.computerUse.*for mouse, keyboard, screenshot, and screen recording - No PTY / SSH: Blaxel has no interactive terminal or SSH access. Daytona supports PTY sessions and SSH access with token-based auth
- No webhooks: Blaxel has no webhook/events API. Daytona supports lifecycle event webhooks configured via dashboard
- No sandbox resize: Blaxel does not support hot-resizing CPU/memory/disk. Create a new sandbox with different config instead
- Image builder: Daytona has a declarative
Imagebuilder with chainable methods. Blaxel uses standard Dockerfiles deployed viabl deploy - MCP integration: Every Blaxel sandbox has a built-in MCP server at
https://<SANDBOX_URL>/mcp. UseblTools(['sandbox/my-sandbox'])for framework integrations. Daytona requires separate MCP server configuration - Code interpreter: Both support stateful code execution. Blaxel's
CodeInterpreterclass uses Jupyter and supports multiple languages. Daytona'scodeInterpreteris Python-only - Log streaming: Blaxel provides
onLog/onStdout/onStderrcallbacks onprocess.exec(), plusprocess.streamLogs()andprocess.logs()with type filtering. Daytona uses session-based command logs - Volumes: Both support persistent volumes. Daytona volumes are FUSE-based S3-compatible with concurrent multi-sandbox mounting. Blaxel volumes are persistent block storage with simpler CRUD via
VolumeInstance
Complete migration example
TypeScript
BEFORE (Daytona):
import { Daytona } from '@daytonaio/sdk'
const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY })
const sandbox = await daytona.create({
image: 'debian:slim',
resources: { memory: 2, cpu: 1 },
envVars: { NODE_ENV: 'production' },
labels: { project: 'demo' },
target: 'us',
})
await sandbox.fs.uploadFile(Buffer.from('console.log("hello")'), '/app/index.js')
const result = await sandbox.process.executeCommand('node /app/index.js')
console.log(result)
await sandbox.delete()
AFTER (Blaxel):
import { SandboxInstance } from '@blaxel/core'
const sandbox = await SandboxInstance.createIfNotExists({
name: 'my-sandbox',
image: 'blaxel/node:latest',
region: 'us-pdx-1',
memory: 2048,
envs: [{ name: 'NODE_ENV', value: 'production' }],
labels: { project: 'demo' },
})
await sandbox.fs.write('/app/index.js', 'console.log("hello")')
const result = await sandbox.process.exec({
command: 'node /app/index.js',
workingDir: '/app',
waitForCompletion: true,
})
await sandbox.delete()
Python
BEFORE (Daytona):
import asyncio
from daytona_sdk import Daytona, CreateSandboxParams, Resources
async def main():
daytona = Daytona(api_key=os.environ["DAYTONA_API_KEY"])
sandbox = await daytona.create(CreateSandboxParams(
image="debian:slim",
resources=Resources(cpu=1, memory=2),
env_vars={"NODE_ENV": "production"},
labels={"project": "demo"},
target="us",
))
await sandbox.fs.upload_file(b'print("hello")', "/app/main.py")
result = await sandbox.process.execute_command("python3 /app/main.py")
print(result)
await sandbox.delete()
asyncio.run(main())
AFTER (Blaxel):
import asyncio
from blaxel.core import SandboxInstance
async def main():
sandbox = await SandboxInstance.create_if_not_exists({
"name": "my-sandbox",
"image": "blaxel/py-app:latest",
"region": "us-pdx-1",
"memory": 2048,
"envs": [{"name": "NODE_ENV", "value": "production"}],
"labels": {"project": "demo"},
})
await sandbox.fs.write("/app/main.py", 'print("hello")')
result = await sandbox.process.exec({
"command": "python3 /app/main.py",
"working_dir": "/app",
"wait_for_completion": True,
})
await sandbox.delete()
asyncio.run(main())
Resources
- Blaxel docs: https://docs.blaxel.ai
- Blaxel sandbox images: https://github.com/blaxel-ai/sandbox/tree/main/hub
- Daytona docs: https://www.daytona.io/docs/en/
- SDK reference (TypeScript):
@blaxel/core— see ./references/sdk-typescript.md - SDK reference (Python):
blaxel— see ./references/sdk-python.md