SecondBox

module
v0.26.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT

README

SecondBox

Self-hosted, isolated Sandboxes with durable workspaces.

CI Release License

SecondBox runs untrusted workloads — AI agents, user code, plugins, CI jobs, long-lived dev environments — inside isolated Sandboxes whose filesystems survive between sessions. The Firecracker backend boots each Sandbox as a microVM on KVM hosts; the gVisor backend serves Linux hosts without KVM, including Kubernetes nodes.

  • Workload isolation. Firecracker uses hardware virtualization on KVM hosts. gVisor uses a userspace-kernel sentry on hosts without KVM.
  • Durable workspaces. A Sandbox keeps its disk across stops, restarts, and generations. Snapshot it and restore in place.
  • Interactive terminals. PTYs support raw mode, resize forwarding, and bounded reconnect.
  • Scoped access. Application-owned resources belong to one Tenant and Subject. Application tokens carry fixed scopes and explicit Profile grants.
  • Immutable Profiles. Operators fix image, resource defaults and optional ceilings, lifecycle, network, and port policy. Each Sandbox pins the revision resolved at creation.
  • Self-hosted. One unprivileged control plane, PostgreSQL, and one or more privileged runners you place yourself.

[!NOTE] SecondBox is a network service with separately deployed Runners. Clients use the control-plane API; authorities explicitly granted direct Port transport also connect to the admitted Runner endpoint.

How it works

A Sandbox is the durable public resource; the Instance running it is replaceable compute fenced to one Sandbox generation. Each Sandbox is placed at creation on one home Runner, whose reflink-capable filesystem owns that Sandbox's Workspace and local Snapshots. Ordinary lifecycle and automatic recovery never relocate it. An operator may relocate a stopped Sandbox with no retained Snapshots through the explicit asynchronous relocation operation.

secondboxd stores desired state in PostgreSQL. Workspace bytes stay on the owning Runner except while secondboxd forwards a bounded, in-memory stream for an explicit stopped-Sandbox relocation; it never persists those bytes.

Getting started

Guided single-host install

The guided installer turns one qualified Linux amd64 systemd host into a loopback-only development deployment with PostgreSQL, the control plane, and one same-host Firecracker Runner. It verifies a published release and records every accepted path and authority decision. With local tenancy selected (the guided default), it creates a Tenant and Subject, configures the platform CLI session for secondbox run, and verifies hello-world guest execution. Declining tenancy leaves a platform session and record-only readiness checks.

The host needs Docker Engine with Compose v2, cgroup v2, accessible KVM and TUN devices, hardware virtualization, at least 6 logical CPUs and 12 GiB of memory. Runner storage needs at least 65 GiB: 50 GiB for the durable-coding Workspace, approximately 11 GiB for verified execution assets, and a 4 GiB margin. Use a dedicated non-root XFS/Btrfs filesystem with that capacity, or let the installer create a fully allocated Btrfs image of at least 65 GiB; the image choice additionally needs its full allocation plus the reviewed control-service, download, and backing reserves on /var/lib. Check the host without changing it:

secondbox-deploy install --check

To fetch the small published bootstrap and run the wizard:

curl -fsSL https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh | sh

v0.16.0 updates a v0.15.0 deployment in place: it keeps Runner protocol generation 5, the database schema, and the fixed-Profile guest bundle. Stop active Sandboxes and retain a database and Runner-storage backup before updating; see the v0.16.0 release notes. Deployments older than v0.15.0 must first cross the boundary in the v0.15.0 release notes, and earlier release boundaries still apply.

To update a compatible completed guided deployment after stopping every Sandbox, pass its recorded operation directory to the latest bootstrap:

curl -fsSL https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh \
  | sh -s -- update /absolute/path/to/secondbox-install-operation

For a compatible target, run update --check first to validate compatibility, drift, and staging capacity. Updates preserve recorded authority, Runner identity, and durable data. Database, Runner protocol, and execution-bundle changes can require recreation; the update guide records those boundaries.

The bootstrap downloads only the release-pinned Linux amd64 secondbox-deploy binary to a temporary directory, verifies its embedded SHA-256 digest, and dispatches the requested install or update operation. It does not invoke sudo or modify the host itself. The installer shows its exact privileged action list before asking sudo to run its narrow host-preparation entry point.

If you do not pipe scripts into a shell, download and inspect the same assets first:

install_dir=$(mktemp -d)
cd "$install_dir"
curl -fLO https://github.com/SecondStack-AI/SecondBox/releases/latest/download/install.sh
curl -fLO https://github.com/SecondStack-AI/SecondBox/releases/latest/download/SHA256SUMS
grep '  install.sh$' SHA256SUMS | sha256sum -c -
less install.sh
sh install.sh

See guided single-host installation for every prerequisite, wizard choice, created resource, recovery command, and durability boundary.

Other deployment paths

The guided path is deliberately Linux amd64, same-host, loopback-only, and development-mode. It does not replace these separate workflows:

  • Download individual release binaries or SDKs from the latest release when you only need a client.
  • Follow deployment and runtime operations for production authority, remote Runners, or a manually reviewed same-host topology.
  • For a manually reviewed same-host Runner declaration, start with secondbox-deploy runner-template --backend firecracker (or --backend gvisor for a host without KVM); it emits every required Runner-host path and authority field without installing anything.
  • Use just deploy-development-up .tmp/secondbox-development for control-plane-only source-checkout development. That topology has synthetic development artifact identity and cannot execute a Sandbox until a real Runner and verified assets are configured.
  • Follow the microVM image pipeline and release distribution when producing or independently materializing execution assets.
Control-plane-only start
just deploy-development-up .tmp/secondbox-development

This creates one private, versioned secondbox.toml, generates unique referenced secrets, compiles a protected environment transport, and starts the reviewed loopback PostgreSQL and control-plane topology. The generated environment is never operator input. This topology is useful for control-plane development and API work, but it cannot execute a Sandbox. Read deployment and runtime operations before exposing the API, configuring production, or enrolling a Runner.

Install the CLI

Use the release binary above, or build the current checkout from source:

go build -o ./dist/secondbox ./cmd/secondbox
Log in once
secondbox platform login \
  --url https://secondbox.example.com \
  --token "$SECONDBOX_PLATFORM_TOKEN" \
  --tenant-ref acme \
  --subject-ref alice

Credentials are verified against the deployment before anything is written, then stored at mode 0600. Every later command resolves them from the first source that has them: an explicit flag, then SECONDBOX_URL / SECONDBOX_TOKEN / SECONDBOX_TENANT_REF / SECONDBOX_SUBJECT_REF, then that file. secondbox whoami shows what resolved and from where; it never prints the token.

Run something
secondbox run durable-coding --image registry.example/secondbox/agent:stable -- python3 -c 'print("hello from SecondBox")'

Using the CLI

Create and size a Sandbox

run creates a Sandbox, waits for readiness, executes a command, and deletes it. Add --keep to retain its Workspace and report its identifier:

secondbox run durable-coding --image registry.example/secondbox/agent:stable --name mybox --keep -- true
secondbox run durable-coding --image registry.example/secondbox/agent:stable --cpus 2 --memory 4GiB --disk 20GiB -- python3 -c 'print("hello")'
secondbox get mybox

Use --size small|medium|large for client presets, or request individual axes within the Profile's ceilings, Tenant/Subject quota, and Runner capacity. Disk requests round up, capped by a finite Profile ceiling; the resolved allocation appears in get. Snapshot-resume Profiles require their exact shape. See resource sizing for presets, rounding, and operator examples.

For creation without an initial command, create returns the admitted Operation immediately. The Profile determines the initial state; check readiness before exec:

secondbox create durable-coding --image registry.example/secondbox/agent:stable --size small --name worker
secondbox get worker
secondbox ls
Work with files, commands, and ports

A new Workspace starts empty. Copy your files explicitly; your local checkout is never mounted or copied implicitly. These examples assume ./src exists:

secondbox cp -r ./src mybox:/workspace/src
secondbox exec mybox --shell -- 'printf "hello\n" > /workspace/out.txt'
secondbox cp mybox:/workspace/out.txt ./out.txt
secondbox ls-files mybox /workspace
secondbox shell mybox
secondbox ports forward mybox 3000

The port forward listens on 127.0.0.1:3000 until interrupted; run a server on Sandbox port 3000 in another terminal first. Use 8080:3000 for different local and remote ports, or --bind to select the local address. The Profile must permit the remote port. Files use sandbox:/workspace/path operands, and cp -r recursively copies directories.

Friendly verbs taking a Sandbox accept its name or sbx_… identifier. Names use the reserved secondbox.dev/name metadata key, unique per Tenant and Subject until deletion, and resolve through the API from any machine. run and create take a Profile instead.

Guest stdout and stderr stay separate, and the guest's exit status becomes the CLI exit status. run deletes its Sandbox even after a failed command unless --keep is set. --shell accepts one shell command; --stdin forwards buffered input. run PROFILE --tty creates an interactive session, and --keep retains it after disconnection. shell attaches to an existing Sandbox and manages its generation and Lease.

Stop, snapshot, and reuse

Snapshots capture a stopped Sandbox's durable Workspace. Keep the source's disk capacity when cloning; CPU and memory may differ within the target Profile's ceilings:

secondbox stop mybox
secondbox snapshot mybox --name with-deps
secondbox snapshots mybox
secondbox run durable-coding --image registry.example/secondbox/agent:stable --from mybox/with-deps -- cat /workspace/out.txt
secondbox start mybox --image registry.example/secondbox/agent:stable
secondbox stop mybox
secondbox rm mybox

start, stop, and rm wait for their terminal state; --no-wait returns the admitted Operation instead. rm asks for confirmation only on a TTY, and --force skips it. list aliases ls; delete aliases rm. ls includes all states returned by the API and accepts --name for an exact name filter.

run and create accept --from sandbox/snapshot-name or an opaque snp_… identifier. restore SANDBOX REF restores into a stopped Sandbox, while snapshot rm REF deletes a Snapshot; both return an admitted Operation. Snapshot names are unique among ready Snapshots in the source Sandbox.

To install dependencies under /workspace and reuse them, follow the golden-snapshot workflow. It includes a reviewed operator Profile example with registry HTTPS access; the standard durable-coding bundle allows only the platform gateway.

Terminal presentation and scripts

Global presentation flags precede the command:

secondbox --output plain --color never ls
secondbox --output json get mybox

Eligible terminals receive compact summaries and bounded tables; pipes retain API JSON. --output plain selects an unstyled human view, and --output json preserves API response bytes. For lifecycle mutations, JSON is the admitted Operation even when the human view waits and shows the resulting resource. Guest streams remain raw; run --json and exec --json explicitly request the ExecOutcome. NO_COLOR disables automatic color; --accessible selects accessible prompts. See the CLI output contract for every classification.

Everything else

Generic aliases remain thin transport over the API. Use repeatable --path, --query, and --header pairs, and --body with a file or - for stdin:

secondbox profiles list
secondbox profiles get --path profileName=durable-coding
secondbox operation listSandboxes --query limit=20

operation OPERATION_ID reaches any published route directly. Generic mutations require the headers specified by that operation; friendly verbs and SDK helpers compute their own idempotency, revision, and generation values.

Full reference: SDK, CLI, and Flue quick starts.

SDKs

Go and TypeScript each provide a handwritten composition layer over generated transports and wire types: idempotency keys, bounded-wait looping, lease keepers that renew in the background, outcome decoding, and run.

client, err := secondboxclient.NewSecondBoxSubjectClient(
    "https://secondbox.example.com", token, "acme", "alice", http.DefaultClient)
if err != nil {
    log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

handle, outcome, err := client.Run(ctx, secondboxclient.RunRequest{
    Profile: "durable-coding",
    Command: secondboxclient.Command{ArgvCommand: &secondboxclient.ArgvCommand{
        Mode: "argv", Executable: "python3", Arguments: []string{"-c", "print('hello')"},
    }},
    DeadlineMilliseconds: 30_000,
    MaximumOutputBytes:   1 << 20,
})
// Run retains any created Sandbox; the caller owns cleanup, including on error.
_ = handle
if err != nil {
    log.Fatal(err)
}
fmt.Print(string(outcome.Result.Stdout))
TypeScript
import { SecondBox, SecondBoxClient } from "@secondstack-ai/secondbox";

const api = new SecondBox(
  new SecondBoxClient("https://secondbox.example.com", token, fetch, "acme", "alice"),
);

const { handle, result } = await api.run({
  profile: "durable-coding",
  command: {
    mode: "argv",
    executable: "python3",
    arguments: ["-c", "print('hello')"],
  },
  deadlineMilliseconds: 30_000,
  maximumOutputBytes: 1_048_576,
  readyTimeoutMilliseconds: 300_000,
});

if (result.kind === "exited") process.stdout.write(result.stdout);

Run never deletes the Sandbox it created in either client — disposal stays your decision.

Repository layout

Path Contents
cmd/secondbox the CLI
cmd/secondboxd unprivileged control plane
runner privileged compute backends, WorkspaceStore, and guest agent
contracts canonical public, runner, and guest-agent protocols
internal domain, API, scheduling, reconciliation, persistence
migrations/postgres database migration lineage
sdk Go and TypeScript clients
deploy Compose, systemd, and deployment examples
docs/design architecture and compatibility contracts
docs/operations installation, backup, diagnostics

Validation

The portable gate needs no KVM and is what CI runs:

just test-non-kvm

Firecracker validation requires a dedicated Linux host with KVM and the configured assets:

just test-firecracker

The external scenario gate joins the HTTP API, PostgreSQL, the runner protocol, and real Firecracker guests. It needs a self-hosted Linux amd64 or arm64 machine with writable KVM and TUN devices, cgroup v2, a separately verified microVM bundle, and an XFS or Btrfs workspace root with reflink support:

SECONDBOX_REQUIRE_QUALIFIED_SCENARIO=1 just test-scenario

See scenario qualification for host setup, automated just qualify tiers, and timing budgets. Runner protocol, lifecycle reconciliation, and Workspace durability changes require the scenario gate. Every commit admitted to main must pass the GitHub-hosted CI workflow. Releases are built locally, uploaded to a private draft, and published as stable GitHub, GHCR, and npm artifacts without rebuilding. See release operator setup.

Security

Runner connections require TLS 1.3, a CA-signed certificate identifying the Runner, and a pre-shared Runner credential. The HTTP API accepts the deployment-wide platform token for operators, persisted tenant-controller credentials for delegated management, and persisted application credentials bound to fixed Tenant/Subject references, exact operation scopes, and named Profile grants. None of these authorities are interchangeable.

Loss of an unbacked home-runner workspace filesystem loses that Sandbox: PostgreSQL recovery cannot reconstruct runner-local data. Back up each Runner's stable identity and workspace root as one consistent unit — see backup and recovery and the threat model.

Documentation

Use the documentation index for architecture, operations, and SDK references. Plans lists unfinished work separately from completed implementations and historical qualification evidence.

License

MIT. Third-party components and execution assets retain their own licenses; see THIRD_PARTY_NOTICES.md.

Directories

Path Synopsis
cmd
secondbox command
secondboxd command
Package deployassets provides the Compose assets compiled into secondbox-deploy.
Package deployassets provides the Compose assets compiled into secondbox-deploy.
examples
gen
internal
api
Package api exposes the versioned standalone SecondBox HTTP contract.
Package api exposes the versioned standalone SecondBox HTTP contract.
assetcatalog
Package assetcatalog imports the components of client-selected execution images.
Package assetcatalog imports the components of client-selected execution images.
cliui
Package cliui owns terminal capability detection and human-facing rendering for the secondbox and secondbox-deploy commands.
Package cliui owns terminal capability detection and human-facing rendering for the secondbox and secondbox-deploy commands.
config
Package config loads required standalone SecondBox process configuration.
Package config loads required standalone SecondBox process configuration.
deployconfig
Package deployconfig compiles the sole operator-edited SecondBox deployment manifest into explicit process environment artifacts.
Package deployconfig compiles the sole operator-edited SecondBox deployment manifest into explicit process environment artifacts.
install
Package install owns the durable authority and orchestration contracts for the guided single-host installer.
Package install owns the durable authority and orchestration contracts for the guided single-host installer.
lifecycle
Package lifecycle computes durable desired-state reconciliation actions.
Package lifecycle computes durable desired-state reconciliation actions.
observability
Package observability owns fixed-cardinality in-process timing observations.
Package observability owns fixed-cardinality in-process timing observations.
pagination
Package pagination defines provider-neutral opaque traversal cursors.
Package pagination defines provider-neutral opaque traversal cursors.
ports
Package ports defines SecondBox control-plane persistence boundaries.
Package ports defines SecondBox control-plane persistence boundaries.
reconcile
Package reconcile defines restart-safe assignment recovery decisions.
Package reconcile defines restart-safe assignment recovery decisions.
runnercontrol
Package runnercontrol authenticates and validates runner-initiated protocol sessions.
Package runnercontrol authenticates and validates runner-initiated protocol sessions.
runnercontrol/conformance
Package conformance provides reusable runner protocol state-machine qualification.
Package conformance provides reusable runner protocol state-machine qualification.
scheduler
Package scheduler selects compatible runners and persists fenced assignment authority.
Package scheduler selects compatible runners and persists fenced assignment authority.
service
Package service validates and coordinates standalone SecondBox authority.
Package service validates and coordinates standalone SecondBox authority.
store
Package store implements PostgreSQL-backed SecondBox control-plane authority.
Package store implements PostgreSQL-backed SecondBox control-plane authority.
store/lifecycleprojection
Package lifecycleprojection owns PostgreSQL projections shared by durable lifecycle transitions and the runner evidence transactions that establish those transitions' prerequisites.
Package lifecycleprojection owns PostgreSQL projections shared by durable lifecycle transitions and the runner evidence transactions that establish those transitions' prerequisites.
store/rowlock
Package rowlock owns the invariant PostgreSQL lock order for durable resource mutations.
Package rowlock owns the invariant PostgreSQL lock order for durable resource mutations.
subjectcleanup
Package subjectcleanup coordinates restart-safe Subject expiry and teardown.
Package subjectcleanup coordinates restart-safe Subject expiry and teardown.
worknotify
Package worknotify turns PostgreSQL commit notifications into process-local, coalesced wakeup hints.
Package worknotify turns PostgreSQL commit notifications into process-local, coalesced wakeup hints.
migrations
postgres
Package postgresmigrations applies the embedded, immutable SecondBox schema lineage.
Package postgresmigrations applies the embedded, immutable SecondBox schema lineage.
pkg
buildinfo
Package buildinfo contains immutable release identity injected at link time.
Package buildinfo contains immutable release identity injected at link time.
contracts
Package contracts defines the public and persisted SecondBox domain language.
Package contracts defines the public and persisted SecondBox domain language.
egressattribution
Package egressattribution frames Runner-established execution identity before guest bytes.
Package egressattribution frames Runner-established execution identity before guest bytes.
networkpolicycontract
Package networkpolicycontract defines the provider-neutral logical gateway syntax shared by deployment tooling and every Runner network backend.
Package networkpolicycontract defines the provider-neutral logical gateway syntax shared by deployment tooling and every Runner network backend.
portdirect
Package portdirect defines the bounded caller-facing handshake that precedes every byte of a direct SecondBox data-plane connection.
Package portdirect defines the bounded caller-facing handshake that precedes every byte of a direct SecondBox data-plane connection.
releasecontract
Package releasecontract defines the public, provider-neutral identity of a SecondBox release.
Package releasecontract defines the public, provider-neutral identity of a SecondBox release.
releaseverify
Package releaseverify retrieves and independently verifies a public release artifact manifest without a repository checkout.
Package releaseverify retrieves and independently verifies a public release artifact manifest without a repository checkout.
resourceapply
Package resourceapply checks and converges declarative SecondBox resources.
Package resourceapply checks and converges declarative SecondBox resources.
standardresources
Package standardresources materializes release-owned resource bundles from a verified release artifact manifest and explicit deployment bindings.
Package standardresources materializes release-owned resource bundles from a verified release artifact manifest and explicit deployment bindings.
sdk
tests
openapicheck
Package openapicheck validates live HTTP responses against the canonical OpenAPI contract.
Package openapicheck validates live HTTP responses against the canonical OpenAPI contract.
scenario/stress command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL