Go SDK for BoxLite — an embeddable virtual machine runtime for secure, isolated code execution.
Access the agent API through Box.Agents()
for session and turn methods, grouped options and error handling.
go get github.com/boxlite-ai/boxlite/sdks/go
go run github.com/boxlite-ai/boxlite/sdks/go/cmd/setupRequires Go 1.24+ with CGO enabled. The setup step downloads the prebuilt native library and header into the module directory in your Go module cache (one-time). Set GITHUB_TOKEN to avoid API rate limits.
package main
import (
"context"
"fmt"
"log"
boxlite "github.com/boxlite-ai/boxlite/sdks/go"
)
func main() {
rt, err := boxlite.NewRuntime(
boxlite.WithImageRegistry(boxlite.ImageRegistry{
Host: "registry.example.com",
Auth: boxlite.ImageRegistryAuth{
Username: "user",
Password: "password",
},
}),
)
if err != nil {
log.Fatal(err)
}
defer rt.Close()
ctx := context.Background()
box, err := rt.Create(ctx, "alpine:latest",
boxlite.WithName("my-box"),
boxlite.WithCPUs(1),
boxlite.WithMemory(512),
boxlite.WithNetwork(boxlite.NetworkSpec{
Outbound: boxlite.OutboundNetworkSpec{
Mode: boxlite.NetworkModeEnabled,
AllowNet: []string{"api.openai.com"},
},
}),
boxlite.WithSecret(boxlite.Secret{
Name: "openai",
Value: "sk-...",
Hosts: []string{"api.openai.com"},
}),
)
if err != nil {
log.Fatal(err)
}
if err := box.Start(ctx); err != nil {
log.Fatal(err)
}
fmt.Println("Box started successfully!")
}archivePath, err := box.Export(ctx, "/var/lib/my-app/archives")
if err != nil {
log.Fatal(err)
}
// An empty name uses the same unnamed-box behavior as Create without
// WithName. The imported box receives a new ID and starts stopped.
restored, err := rt.Import(ctx, archivePath, "")
if err != nil {
log.Fatal(err)
}
defer restored.Close()A local runtime treats the archive as trusted because local applications own both the runtime and archive. A REST runtime uploads the file and relies on the server's untrusted-upload policy.
Export and Import never delete the archive. The caller owns its retention and must explicitly remove it when it is no longer needed.
rt.Images() holds the images a runtime can boot from: the local cache on a
runtime from NewRuntime, the server's catalog on one from NewRest. List,
Get and Remove run unchanged against either.
ctx := context.Background()
images, err := rt.Images()
if err != nil {
log.Fatal(err)
}
defer images.Close()
cached, err := images.List(ctx)
if err != nil {
log.Fatal(err)
}
for _, image := range cached {
fmt.Println(image.Repository, image.Tag, image.ID)
}
detail, err := images.Get(ctx, "docker.io/library/alpine")
if err != nil {
log.Fatal(err)
}
for _, version := range detail.Versions {
fmt.Println(detail.Name, version.Digest, version.RecordedAt)
}
if err := images.Remove(ctx, "docker.io/library/alpine"); err != nil {
log.Fatal(err)
}Pull fills a local cache ahead of time. A REST runtime refuses it with
ErrUnsupported, because creating a box there pulls the image:
pull, err := images.Pull(ctx, "alpine:latest")
if err != nil {
log.Fatal(err)
}
fmt.Println(pull.Reference, pull.ConfigDigest, pull.LayerCount)| Method | Local runtime | REST runtime |
|---|---|---|
Pull |
Pulls into the cache | ErrUnsupported: creating a box pulls |
List |
One row per cached reference | Catalog alias and digest references |
Get |
The cache entries under the name | The catalog entry with its versions |
Remove |
Forgets the name; layers stay | Removes the catalog entry; ErrInvalidState while a box can boot from it |
Usage |
ErrUnsupported |
ImageUsage{Count, Limit, KnownBytes} |
CreateAlias |
ErrUnsupported |
Creates a new alias; existing names conflict |
DeleteAlias |
ErrUnsupported |
Removes only the named alias |
Get and Remove take an image name such as "docker.io/library/alpine". A
reference with a tag or digest fails with ErrInvalidArgument, since a remove
takes every tag of the name; a name the runtime does not hold fails with
ErrNotFound. ImageDetail.Versions is newest first, and
ImageVersion.SizeBytes is nil when the size is unknown. Locally, a box built
from a removed image reads the image's configuration from the registry when it
next starts.
CreateAlias(ctx, name, alias, digest) creates a new catalog binding to an
existing ready digest. An existing name returns ErrAlreadyExists, including
an identical binding. DeleteAlias(ctx, name, alias) removes only the binding;
an absent alias returns ErrNotFound. Neither operation changes registry tags,
versions or existing boxes. Local runtimes return ErrUnsupported.
ImageDetail.Aliases and ImageVersion.Aliases hold catalog aliases. History
entries are ImageAliasEvent values with Alias and RecordedAt. Registry
tags remain in image references and ImageInfo.Tag.
On a runtime from NewRest, images.Registries() after rt.Images() manages the logins the server
pulls private images with. A runtime from NewRuntime returns
ErrUnsupported: pass WithImageRegistries instead.
images, err := rt.Images()
if err != nil {
log.Fatal(err)
}
defer images.Close()
registries, err := images.Registries()
if err != nil {
log.Fatal(err)
}
defer registries.Close()
login, err := registries.Create(ctx, boxlite.NewRegistryCredential{
RegistryHost: "ghcr.io",
RepositoryPrefix: "acme/", // "" for the whole registry
Username: "acme-bot",
Password: os.Getenv("GHCR_TOKEN"),
})
if err != nil {
log.Fatal(err)
}
// ... boxes created from ghcr.io/acme/... now pull with this login ...
if err := registries.Remove(ctx, login.ID); err != nil {
log.Fatal(err) // ErrInvalidState while a box still pulls through it
}| Method | Returns or fails with |
|---|---|
List |
Every login, oldest first |
Create |
The login; ErrAlreadyExists while one is held for the same registry and prefix |
Remove |
ErrInvalidState naming the boxes while one pulls through it; ErrNotFound for an unknown id; ErrInvalidArgument for an id that is not a UUID |
The password is sent once and never returned: RegistryCredential has no
password field, and NewRegistryCredential prints Password:[redacted] with
%v, %+v and %#v.
-
WithNetwork(boxlite.NetworkSpec{Outbound: boxlite.OutboundNetworkSpec{Mode: boxlite.NetworkModeEnabled, AllowNet: []string{"api.openai.com"}}})restricts outbound traffic while keeping networking enabled. -
WithNetwork(boxlite.NetworkSpec{Outbound: boxlite.OutboundNetworkSpec{Mode: boxlite.NetworkModeDisabled}})disables the guest network interface entirely. -
WithNetwork(boxlite.NetworkSpec{Inbound: boxlite.InboundNetworkSpec{Mode: boxlite.NetworkModeEnabled}})permits proxy access subject to each tunnel's policy. Inbound access is disabled by default, including whenInbound.Modeis unset. The two directions are independent. -
The deprecated flat fields
NetworkSpec{Mode, AllowNet}still configure the outbound direction. Setting them together withOutboundis rejected when the options are built.NetworkInfo.ModeandNetworkInfo.AllowNetlikewise remain readable as views ontoNetworkInfo.Outbound. -
WithPort(boxlite.PortSpec{Guest: 3000})publishes TCP locally on an OS-selected host port; after checkingBoxInfo.Network != nil, read the concrete binding fromNetwork.PublishedPorts. -
A nil
PublishedPortsslice means the current handle does not know the bindings; a non-nil empty slice means there are no active publications.Box.Info,Runtime.GetInfo, andRuntime.ListInfouse callback-backed runtime operations and honor context cancellation. -
WithSecret(boxlite.Secret{...})configures host-side HTTPS secret substitution;Placeholderdefaults to<BOXLITE_SECRET:{Name}>. -
Container capabilities live under advanced options:
advanced, err := boxlite.NewAdvancedBoxOptions() if err != nil { log.Fatal(err) } defer advanced.Close() if err := advanced.SetCapabilities(boxlite.ContainerCapabilities{ Add: []string{"NET_ADMIN"}, Drop: []string{"NET_RAW"}, }); err != nil { log.Fatal(err) } box, err := runtime.Create(ctx, "alpine:latest", boxlite.WithAdvancedOptions(advanced))
Port publication is local-only. Remote runtimes reject it with guidance to use
the existing network tunnel API. Each tunnel handle is one-shot.
OCI EXPOSE metadata does not publish ports.
The same route workflow works with local and remote runtimes:
box.Network() (*Network, error)returns box-scoped network operations.network.Tunnel(ctx, target, opts) (*BoxTunnel, error)prepares a tunnel.targethas the concrete standard-library type*net.TCPAddrwhose IP is localhost or the guest IP for now; an empty IP means localhost.boxlite.DefaultTunnelOptions()is public, andboxlite.TunnelOptions{IsPublic: false}(also the zero value) requests private cloud access.tunnel.URI()andtunnel.Descriptor()stay readable afterConnectorForward.tunnel.Descriptor()returnsURI,IsPublic, andToken(full once, masked on later preparations).network.RevokeTunnel(ctx, port) errorwithdraws the port's tunnel. It is safe to repeat and leaves open connections running.TCPListenAddress(host, port)andUnixListenAddress(path)return validated standardnet.Addrvalues fortunnel.Forward(ctx, addr). The returnedTunnelForwarderhasAddr() net.Addr,Wait(ctx) error, and repeatableClose() error; canceling a wait context does not close the listener.tunnel.URI() (string, error)returns the public URL of a remotely served tunnel, or an empty string for a local one.tunnel.Connect(ctx) (net.Conn, error)consumes the prepared tunnel and opens its connection.
Choose Connect or Forward; a forwarder reuses the prepared declaration and
opens a fresh authenticated connection per client. It cannot undo revocation.
See private tunnel usage.
This differs from WithPort, which creates a persistent,
local-only host listener that accepts repeated connections.
On a hosted runtime the organization can route a name of its own
(demo.apps.example) to a box port; see
Domains.
rt.Domains() (*Domains, error)returns the handle; a local runtime returns anErrUnsupportederror. Release it withClose().domains.Bind(ctx, "demo.apps.example", boxlite.DomainTarget{Type: boxlite.DomainTargetBox, Box: "web-a", Port: 3000}) (*Domain, error)requires an owned full hostname; request new names in Dashboard > Domains first.Boxtakes an id or name.domains.List(ctx) (*DomainList, error)returns every held fullHostname(Targetnil while unbound) and the allowance.domains.Unbind(ctx, "demo.apps.example") errorrequires the full hostname and stops routing it; the organization keeps the name.
Domain has Hostname, URL and Target; there is no short Name.
DomainTargetApp with App reserves an App reference; servers currently reject
App binding with app_target_not_supported.
Each call is one blocking request, like Network.Tunnel: ctx is checked
before and after waiting for another call on the same handle. Cancellation
observed after that wait prevents the request; it does not interrupt a request
already in flight.
box.SSH() configures, observes, and disables the box's guest SSH listener.
SSH defaults to disabled when no state is saved; the SDK is not an SSH client.
hostKey, err := os.ReadFile("host_ed25519") // unencrypted OpenSSH private key
if err != nil { log.Fatal(err) }
status, err := box.SSH().Configure(ctx, boxlite.SSHConfig{
ListenAddress: "0.0.0.0:2222",
HostPrivateKey: string(hostKey),
Accounts: []boxlite.SSHAccount{{
Login: "alice",
AuthorizedKeys: []string{"ssh-ed25519 AAAA... alice@laptop"},
// CA: &boxlite.SSHCAConfig{PublicKey: "ssh-ed25519 AAAA...", Principal: "alice"},
}},
})
if err != nil { log.Fatal(err) }
fmt.Println(status.Generation, status.HostKeyFingerprint)
current, err := box.SSH().Status(ctx)
if err != nil { log.Fatal(err) }
fmt.Println(current.Enabled)
disabled, err := box.SSH().Disable(ctx)
if err != nil { log.Fatal(err) }
fmt.Println(disabled.Enabled, disabled.Generation) // false, generation kept
enabled, err := box.SSH().Enable(ctx) // reuse saved configuration
if err != nil { log.Fatal(err) }
fmt.Println(enabled.Enabled) // trueSSH has no Close: it refers to its Box and shares the box's and
runtime's lifetime.
- Lifecycle:
Configurereplaces the whole configuration, restarts the listener, and may start the box. WithWithCmdorWithEntrypoint, callbox.Startfirst orConfigurereturnsErrInvalidState.Status,EnableandDisablenever start the box.Enablerequires a running box and saved configuration, works locally and over REST, and preserves healthy connections. On an idle local box,Statusreports disabled with generation zero, andDisablereturnsErrInvalidState. On a running box,Disableis safe to repeat and keeps the generation. - Keys: the guest validates keys and stores them on the container disk. A
rejected configuration returns
ErrInvalidArgumentwithout echoing key material.%v,%+v,%s, and%#vredact keys and CA values; JSON encoding of these types does not. - Errors: a closed
BoxreturnsErrInvalidState; a closed runtime returnsErrRuntimeClosed. Canceling the context returns promptly but does not undo a configuration the guest already applied, so do not retryConfigureautomatically. - Connecting: SSH publishes no host port. Reach
ListenAddress's port withWithPort(boxlite.PortSpec{Guest: 2222})on a local runtime, or a service tunnel such asnetwork.Tunnel(ctx, &net.TCPAddr{Port: 2222}, boxlite.DefaultTunnelOptions()). - Native library: SSH needs a
boxlite.hand native library that exportboxlite_box_ssh_configure,_enable,_status, and_disable.cmd/setupfetches the pair matching your module version; an older library fails to link.
See the SSH guide for listener behavior, authentication rules, and the HTTP contract.
Build from source (requires Rust toolchain):
# From the project root
make dev:go
# Run tests
cd sdks/go && go test -tags boxlite_dev -v ./...Apache-2.0