> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boxlite.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Go quickstart

> The shortest path to running your first command in an isolated microVM sandbox from Go: a real exit code and stdout, with no virtual machine to manage.

Use the Go module `github.com/boxlite-ai/boxlite/sdks/go` to run a command in an isolated microVM sandbox within minutes, without managing virtual machines, images, or networking yourself. Go has no `SimpleBox`-style one-line wrapper — you build the runtime and the box explicitly, in two calls.

***

## Prerequisites

* A machine with hardware virtualization — see [Installation](/getting-started/installation#platform-and-virtualization-requirements-common-to-all-sdks) for the supported platforms.
* Go 1.24 or newer, with CGO enabled (the default).

Install the module, then run the one-time setup step that fetches the prebuilt native library:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
go get github.com/boxlite-ai/boxlite/sdks/go
go run github.com/boxlite-ai/boxlite/sdks/go/cmd/setup
```

`cmd/setup` detects your platform and SDK version and downloads `libboxlite.a` plus `boxlite.h` from GitHub Releases straight into your Go module cache, so a later `go build` links against it with no separate install step. It prints `Setup complete.` when done; if the download stalls, see [Troubleshooting](#setup-download-times-out).

***

## Quick Example

Write the following into `main.go`, then run `go build && ./<binary>`.

```go theme={"theme":{"light":"github-light","dark":"github-dark"}}
package main

import (
	"context"
	"fmt"
	"log"

	boxlite "github.com/boxlite-ai/boxlite/sdks/go"
)

func main() {
	ctx := context.Background()

	// 1. Get the runtime (synchronous construction; no .Close() needed until you're done with it)
	rt, err := boxlite.NewRuntime()
	if err != nil {
		log.Fatal(err)
	}
	defer rt.Close()

	// 2. Create a box (default rootfs is set by the image argument)
	box, err := rt.Create(ctx, "alpine:latest")
	if err != nil {
		log.Fatal(err)
	}
	defer box.Close()

	// 3. Execute a command — Exec implicitly starts the box on first call
	res, err := box.Exec(ctx, "echo", "Hello from BoxLite!")
	if err != nil {
		// A nil res accompanies a non-nil err: check err before touching res
		log.Fatal(err)
	}

	// 4. A non-zero exit code does not become an error — check it yourself
	if res.ExitCode != 0 {
		fmt.Printf("command failed (exit=%d): %s", res.ExitCode, res.Stderr)
		return
	}
	fmt.Print(res.Stdout)
}
```

Verified output from a real run against the published `v0.9.7` module (`go get` + `cmd/setup`, no local build):

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Hello from BoxLite!
```

***

## Parameters and Returns (Core API)

### `boxlite.NewRuntime` / `*Runtime` (runtime handle)

| Function / Method                   | Signature                                                        | Description                                                       |
| ----------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------- |
| `NewRuntime`                        | `func(opts ...RuntimeOption) (*Runtime, error)`                  | Build a runtime. With no options, uses the default home directory |
| `WithHomeDir`                       | `func(dir string) RuntimeOption`                                 | Override the runtime's home directory                             |
| `(*Runtime) Create`                 | `func(ctx, image string, opts ...BoxOption) (*Box, error)`       | Create a box from an image                                        |
| `(*Runtime) GetOrCreate`            | `func(ctx, image string, opts ...BoxOption) (*Box, bool, error)` | Get by name or create; the `bool` is `true` when newly created    |
| `(*Runtime) Get`                    | `func(ctx, idOrName string) (*Box, error)`                       | Get an existing box by ID or name                                 |
| `(*Runtime) ListInfo`               | `func(ctx) ([]BoxInfo, error)`                                   | List all boxes                                                    |
| `(*Runtime) Remove` / `ForceRemove` | `func(ctx, idOrName string) error`                               | Delete permanently (`ForceRemove` also removes a running box)     |
| `(*Runtime) Metrics`                | `func(ctx) (*RuntimeMetrics, error)`                             | Runtime-wide aggregate metrics                                    |
| `(*Runtime) Close`                  | `func() error`                                                   | Release the runtime; call once you are done creating boxes        |

### `*Box` (box handle)

| Method        | Signature                                                    | Description                                                        |
| ------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ |
| `ID` / `Name` | `func() string`                                              | Identifiers (synchronous)                                          |
| `Info`        | `func(ctx) (*BoxInfo, error)`                                | Box metadata                                                       |
| `Start`       | `func(ctx) error`                                            | Explicit start; idempotent                                         |
| `Exec`        | `func(ctx, name string, arg ...string) (*ExecResult, error)` | Run a command and wait for it to finish                            |
| `Command`     | `func(name string, arg ...string) *Cmd`                      | Build a command in the shape of `os/exec`                          |
| `Metrics`     | `func(ctx) (*BoxMetrics, error)`                             | Per-box metrics                                                    |
| `Stop`        | `func(ctx) error`                                            | Stop the VM (rootfs preserved unless `auto_delete` says otherwise) |
| `Close`       | `func() error`                                               | Release the handle                                                 |

> **A failed start returns `(nil, err)`.** Reading `res.ExitCode` before checking `err` on a failed `Exec` call is a nil-pointer dereference, not a graceful zero value.

### `*Cmd` (built via `Command`)

| Method           | Signature                   | Description                            |
| ---------------- | --------------------------- | -------------------------------------- |
| `Run`            | `func(ctx) error`           | Run and wait                           |
| `Output`         | `func(ctx) ([]byte, error)` | Run and return stdout                  |
| `CombinedOutput` | `func(ctx) ([]byte, error)` | Run and return stdout+stderr merged    |
| `ExitCode`       | `func() int`                | Exit code after `Run`/`Output` returns |

> **This differs from the standard library.** `os/exec`'s `Cmd.Run`/`Output` return a `*exec.ExitError` on a non-zero exit. BoxLite's `Cmd` does not — `Run`/`Output` return `nil` and you read `ExitCode()` yourself, exactly like `Box.Exec`.

### `ExecResult`

| Field               | Type     | Description             |
| ------------------- | -------- | ----------------------- |
| `ExitCode`          | `int`    | Exit code (0 = success) |
| `Stdout` / `Stderr` | `string` | Captured output         |

### Functional options (`BoxOption`, passed to `Create` / `GetOrCreate`)

| Option                              | Signature                                    | Description                                                                                 |
| ----------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `WithName`                          | `func(string) BoxOption`                     | Name the box                                                                                |
| `WithCPUs` / `WithMemory`           | `func(int) BoxOption`                        | vCPUs / memory in MiB                                                                       |
| `WithDiskSize`                      | `func(int) BoxOption`                        | Disk size in GB                                                                             |
| `WithEnv`                           | `func(key, value string) BoxOption`          | One environment variable per call (not a map)                                               |
| `WithVolume` / `WithVolumeReadOnly` | `func(hostPath, guestPath string) BoxOption` | Mount a host directory; the read-only variant is a **separate option**, not a boolean flag  |
| `WithPort`                          | `func(PortSpec) BoxOption`                   | Forward a port                                                                              |
| `WithNetwork`                       | `func(NetworkSpec) BoxOption`                | Network policy                                                                              |
| `WithSecret`                        | `func(Secret) BoxOption`                     | Inject a credential                                                                         |
| `WithAutoDeleteInterval`            | `func(seconds uint32) BoxOption`             | `0` keeps the box after `Stop`; `n > 0` deletes it (immediately, locally) after `n` seconds |
| `WithAutoResumeEnabled`             | `func(bool) BoxOption`                       | Resume-on-access after AutoPause                                                            |
| `WithDetach`                        | `func(bool) BoxOption`                       | Survive the creating process                                                                |

Verified on this machine: with no options set, a fresh box reports `nproc` = `1` (default 1 vCPU) and its guest kernel is `Linux 6.12.76`.

***

## Troubleshooting

### Setup download times out

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
boxlite-setup: download failed: tar: context deadline exceeded (Client.Timeout or context cancellation while reading body)
```

`cmd/setup` downloads a large (\~100+ MB) archive under a fixed timeout. On a slow connection this can fail partway through — reproduced on this machine after successfully extracting `libboxlite.a` but before reaching `boxlite.h`. Re-run `go run github.com/boxlite-ai/boxlite/sdks/go/cmd/setup`; it detects the already-extracted files and resumes from there. It succeeded on the fourth attempt in this environment.

### A failed `Exec` panics with a nil-pointer dereference

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
panic: runtime error: invalid memory address or nil pointer dereference
```

A start failure (for example, a missing binary) returns `(nil, err)` from `Exec`, not a `res` with a nonzero `ExitCode`. Always check `err` before reading `res`:

```go theme={"theme":{"light":"github-light","dark":"github-dark"}}
res, err := box.Exec(ctx, "definitely-not-a-binary")
if err != nil {
	log.Fatal(err) // res is nil here — do not dereference it
}
fmt.Println(res.ExitCode)
```

### A non-zero exit code was not reported as an error

`Exec`, `Cmd.Run`, and `Cmd.Output` all return `nil` for `err` when the command itself ran and simply exited non-zero — check `ExitCode` yourself:

```go theme={"theme":{"light":"github-light","dark":"github-dark"}}
res, _ := box.Exec(ctx, "sh", "-c", "exit 7")
fmt.Println(res.ExitCode) // 7, err was nil
```

### Distinguishing error types

Errors are `*boxlite.Error` with a `.Code`, checkable with the typed helpers:

```go theme={"theme":{"light":"github-light","dark":"github-dark"}}
_, err := rt.Get(ctx, "no-such-box")
if boxlite.IsNotFound(err) {
	fmt.Println("box does not exist")
}
```

`IsNotFound` / `IsAlreadyExists` / `IsInvalidState` / `IsStopped` cover the common cases.

### Build error: `-tags boxlite_dev` behavior

If you are building this repository from source rather than consuming the published module, `go build` alone links against a **prebuilt** library (the one `cmd/setup` downloaded). Building against your own locally-compiled core instead requires `make dev:go` (from the repository root) followed by `go build -tags boxlite_dev ./...` — see [Building from source](/development/building-from-source).

### Startup failure: no hardware virtualization (environment constraint)

* Linux: confirm `/dev/kvm` exists and the current user is in the `kvm` group.
* macOS (Apple Silicon): uses Hypervisor.framework and does **not** need `/dev/kvm`.
* WSL2: enable nested virtualization and install KVM.

This surfaces as an error from `Create`/`Exec`, not a crash — check it like any other returned `error`.

***

## Next Steps

* Run any language or command inside the box: replace the `"echo", "Hello from BoxLite!"` arguments with your own, and `"alpine:latest"` with the image you need.
* For streaming output or stdin, use `Box.Command(...)` (mirrors `os/exec`) or `Box.StartExecution` for the lowest-level `Write` / `Wait` / `Kill` / `Signal` access.
* Other languages: see the [Python quickstart](/getting-started/quickstart-python), the [Node.js quickstart](/getting-started/quickstart-nodejs), and the [Rust quickstart](/getting-started/quickstart-rust).
