@dbx-tools/appkit-sandbox
AppKit integration for Databricks Sandboxes. It manages assigned machines, executes SSH commands, provisions browser services, and persists isolated Projects in Lakebase.
Install
Section titled “Install”bun add @dbx-tools/appkit-sandboxConfigure AppKit
Section titled “Configure AppKit”Register AppKit lakebase() before
databricksSandbox().
The Sandbox plugin reuses that service-principal pool after plugins initialize.
It does not create another pool or request user-scoped Postgres credentials.
Create one
SshRuntime
and share it with the AppKit plugin and Mastra workspace:
import { lakebase } from "@databricks/appkit";import { mastra } from "@dbx-tools/appkit-mastra";import { resolveSshPluginConfig } from "@dbx-tools/appkit-sandbox/config";import { databricksSandbox } from "@dbx-tools/appkit-sandbox/plugin";import { SshRuntime } from "@dbx-tools/appkit-sandbox/runtime";import { databricksSandboxSkillMounts } from "@dbx-tools/appkit-sandbox/skills";import { sshWorkspaceSandbox } from "@dbx-tools/appkit-sandbox/workspace";
const runtime = new SshRuntime( resolveSshPluginConfig({ auto: true, routePrefix: "/sandbox", sandboxLifetimeSeconds: 86_400, reaperIntervalSeconds: 300, }),);
export const plugins = [ lakebase(), databricksSandbox({ runtime }), mastra({ codeMode: true, sandbox: sshWorkspaceSandbox(runtime), localSkillMounts: databricksSandboxSkillMounts(), }),];The Mastra resolver uses a Sandbox only for an explicit SSH-backed operation. Other Code Mode work continues to use Mastra’s normal isolated runtime.
Create a Project
Section titled “Create a Project”Use the add_ssh_project tool instead of creating a proxy route directly:
{ "machineId": "sandbox-id", "name": "demo", "openPath": "index.html", "config": { "tools": { "node": "lts" }, "tasks": { "start": { "run": "[ -f server.mjs ] || exit 0\nif [ -f server.pid ] && kill -0 \"$(cat server.pid)\" 2>/dev/null; then exit 0; fi\nnohup node server.mjs > server.log 2>&1 < /dev/null &\necho $! > server.pid\n" }, "stop": { "run": "[ -f server.pid ] && kill \"$(cat server.pid)\" 2>/dev/null || true\n" } } }}name, path, and port are optional. The runtime generates values that are
unique for the machine, creates ~/projects/<path>, writes mise.toml, runs
mise trust and mise install, and returns the project directory, public
path, loopback port, and openUrl.
Set route: false for a project that does not serve HTTP. Route-less projects
still receive a directory, mise environment, and lifecycle tasks, but they do
not register an AppKit proxy route or return an openUrl. Declare required
built-in services with serviceDependencies, for example
serviceDependencies: ["desktop"]. The runtime starts each dependency before
project setup and writes hidden sandbox:<kind> dependency tasks into the
project’s mise.toml.
Keep hooks.postinstall limited to installation or preparation that belongs
to mise install. Put runtime lifecycle behavior in tasks.start and
tasks.stop; use a visible tasks.uninstall only when the project has
additional teardown beyond stopping its workload. Before invoking any task,
call execute_ssh_command from the returned directory with command: "sh"
and
args: ["-lc", "\"$HOME/.local/bin/mise\" tasks ls --name-only --hidden"].
Evaluate the available tasks and invoke only the task defined for the requested
lifecycle action, using the same command and directory with
args: ["-lc", "\"$HOME/.local/bin/mise\" run <task>"]. Never concatenate
the executable and arguments into command. Start detached servers with
nohup and redirected standard streams. The packaged skills under skills/
specify when and where to invoke each task.
The runtime injects these values into [env]:
PROJECT_NAMEPROJECT_PATHPROJECT_PORT
A matching caller-supplied value is accepted. A conflicting value fails before
the route or Lakebase record changes. Run commands from the returned directory
with mise exec and bind HTTP services to 127.0.0.1:$PROJECT_PORT.
Pass the returned projectId to update the name, path, open path, port, config,
or prefix behavior. An explicit duplicate path or port fails. Omitting a field
during an update retains its stored value.
remove_ssh_project runs mise run stop and then mise run uninstall when
each task exists. Discovery includes hidden tasks for compatibility, but new
uninstall tasks should remain visible. Failures are logged and ignored before
the proxy route, Lakebase record, and project directory are removed. Stopping a
Sandbox preserves Projects. Deleting or expiring a Sandbox removes its Project
records without starting the machine for remote cleanup.
The browser-safe request and response fields are defined by
addSshProjectRequestSchema
and
sshProjectSchema.
Provision machine software
Section titled “Provision machine software”The default machineSoftware configuration installs mise in
~/.local/bin. The installer adds mise activation to the Sandbox user’s Bash
and Zsh startup files, so entering a trusted Project directory activates its
environment.
Add other idempotent setup steps through
SshPluginConfig:
resolveSshPluginConfig({ machineSoftware: [ { name: "mise", checkCommand: 'test -x "$HOME/.local/bin/mise"', installCommand: "...", }, { name: "custom-tool", checkCommand: "command -v custom-tool >/dev/null 2>&1", installCommand: "...", }, ],});Pass an empty array to disable automatic machine setup.
Built-in services
Section titled “Built-in services”Terminal, desktop, and stats are hidden, machine-owned mise projects under
~/projects/.sandbox-services. Their packaged mise.toml files own
installation and start/stop lifecycle tasks. Lakebase stores their
definitions separately from user projects, so they do not appear in project
lists or counts.
Service setup uses a workspace-, machine-, and service-scoped Lakebase advisory
lock. Concurrent in-process calls share one operation; after acquiring the
distributed lock, the runtime rechecks the persisted definition and skips
installation when it is current. Packaged lifecycle tasks recheck remote PID
and version state while holding the lock. A service returned as ready has
completed its process and endpoint readiness check and has a usable
authenticated route.
Terminal and stats start with an explicitly started machine. Desktop remains
on demand unless machine creation passes desktop: true or a project declares
the desktop service dependency. KasmVNC is downloaded only from its pinned,
checksummed official GitHub release.
Machine lifecycle
Section titled “Machine lifecycle”The AppKit server identity must be able to use the Sandbox REST API and manage
SSH keys. The AppKit host must provide OpenSSH on PATH. Direct HTTP and
WebSocket forwarding uses ssh2.
The runtime records ownership and absolute expiration in Lakebase. The
SshRuntime reaper
runs at startup and on the configured interval. It deletes expired assigned
Sandboxes, removes records for missing Sandboxes, and leaves unrelated
workspace Sandboxes unchanged.
Stopping a machine closes local SSH sessions and forwarding bridges. Persisted Projects are restored when the app restarts or the machine starts again.
Authentication and storage
Section titled “Authentication and storage”Workspace REST calls use the current AppKit user’s OBO credentials when
available. fallbackToServicePrincipal can retry a failed REST operation with
the app identity. Machine assignment checks still apply to discovery,
lifecycle, commands, Projects, Mastra tools, and Code Mode.
Assignments and Projects use the AppKit Lakebase service-principal pool through
LakebaseMachineOwnershipStore.
The pool remains owned by the Lakebase plugin. Closing the Sandbox plugin does
not close it.
This assignment layer limits what the application reveals and operates because the Sandbox API does not expose native per-user Sandbox scopes. It does not replace Databricks workspace authorization.
SSH trust
Section titled “SSH trust”SshKeyStore
creates one Ed25519 key pair for each workspace and AppKit user. Only the public
key is registered. Private keys and known-host files remain on the AppKit server
under ~/.dbx-tools/appkit-databricks-sandbox by default.
The first successful connection records the gateway host key. Later connections
reject a changed key.
SshSession
passes the stored file through OpenSSH UserKnownHostsFile. Host verification
is never disabled.
Agent integration
Section titled “Agent integration”sshWorkspaceSandbox()
provides the request-scoped Mastra workspace. The plugin also registers machine,
command, service, and Project tools. Packaged showcase recipes are available
through
databricksSandboxSkillMounts().
See the complete package API reference.