---
lastModified: 2026-10-28
---

# Self-Hosted Orbs

You can self-host [orbs](/docs/orbs) on your own infrastructure. You provide the compute and Amp
turns it into orbs.

If you have compute that can

- quickly start new machines
- put them to sleep and resume them equally fast
- keep them around for a long time
- scale them to zero, so no one has to worry about having a lot of them sleeping

then you can replicate the orb experience within your own infrastructure.

Self-hosted orbs are available to every workspace, and they are free for individuals and teams
(see [Pricing](#pricing)). If you work alone, you can
[create a workspace](/docs/collaborate/workspaces#create-a-workspace) for free.

## How It Works

You run `amp orb provider` on a machine in your network. It connects out to Amp and waits for
requests to spawn, pause, or delete orbs. Only outbound HTTPS is needed; Amp never connects in.

<div class="docs-media-stage">
<img class="docs-transparent-media docs-media-light" width="1774" height="887" src="https://static.ampcode.com/news/self-hosted-orbs-diagram-light.avif" alt="1. amp orb provider opens a connection to ampcode.com. 2. Amp sends events to the provider. 3. The provider spawns an orb per thread. 4. Each orb runs in your infrastructure and connects back to its thread on ampcode.com. No inbound ports are needed." />
<img class="docs-transparent-media docs-media-dark" width="1774" height="887" src="https://static.ampcode.com/news/self-hosted-orbs-diagram-dark.avif" alt="1. amp orb provider opens a connection to ampcode.com. 2. Amp sends events to the provider. 3. The provider spawns an orb per thread. 4. Each orb runs in your infrastructure and connects back to its thread on ampcode.com. No inbound ports are needed." />
</div>

How you satisfy those requests is up to you. The orb provider runs a dispatch program that you
provide, once per job:

```sh
amp orb provider ./dispatch.sh
```

Your dispatch program receives `spawn`, `pause`, and `delete` jobs and creates, resumes, stops,
or removes a machine or container on your platform. On `spawn`, it starts `amp orb bootstrap`
inside the orb. That process connects out to Amp so Amp can set up the environment and run the
agent's tools.

When a member creates a thread on your provider, Amp sends a `spawn` job. When the thread goes
idle or is archived, Amp sends `pause`, and the next message sends `spawn` again to resume the
same orb. Amp sends `delete` when the orb is no longer needed, e.g., when the thread is deleted.

See [What Leaves Your Network](#what-leaves-your-network) for the data sent to Amp and the
outbound connections your network must allow.

## Caution

Not all compute can be turned into orbs. Not all infrastructure meets the requirements to provide a
great orb experience.

That's why Amp-hosted orbs still give the best experience. When they pause, they save disk and
memory, so processes keep running when the orb wakes, and new threads start from a project snapshot
instead of running setup.

If you run orbs on Docker, for example, the experience is different. Docker cannot save memory. So
when a Docker-backed orb wakes, the services behind your [portals](/docs/orbs/portals) start again
when someone opens a portal, and Terminal sessions and Desktop apps are gone.

The [dispatch examples](#dispatch-examples) below differ in what an orb keeps when it pauses:

| Infrastructure          | Memory       | Disk    | Project snapshots                           |
| ----------------------- | ------------ | ------- | ------------------------------------------- |
| Amp-hosted orbs         | Yes          | Yes     | Yes                                         |
| Docker                  | No           | Yes     | On one host                                 |
| Apple containers        | No           | Yes     | On one Mac, with `container` 1.6.0 or later |
| Kubernetes (Helm chart) | No           | Yes     | Yes                                         |
| E2B                     | Yes          | Yes     | Not in the example                          |
| Modal                   | No           | Yes     | Not in the example                          |
| Vercel                  | No           | Yes     | Not in the example                          |
| Daytona                 | No           | Yes     | Not in the example                          |
| exe.dev                 | Yes          | Yes     | Not in the example                          |
| Boat                    | No guarantee | Partial | Not in the example                          |

- **Memory**: processes keep running when the orb wakes, including the services behind
  [portals](/docs/orbs/portals), Terminal sessions, and Desktop apps.
- **Disk**: files and installed software survive a pause, so a waking orb runs `.agents/resume`,
  not `.agents/setup`.
- **Project snapshots**: new threads start from a prepared copy of the orb and skip
  `.agents/setup` while the snapshot is current. See [Project Snapshots](#project-snapshots).

We know not everyone can use Amp-hosted orbs, and some infrastructure does meet every
requirement. That's why we offer self-hosted orbs.

## Getting Started

Create a dispatch program for your platform, register a provider in your workspace, and start
your first thread. For GKE, EKS, or AKS, you can use
[Amp's Helm chart](/docs/orbs/self-hosted/kubernetes) instead of writing a dispatch program.

### Create a Dispatch Program

Save one of the examples below as `dispatch.sh`, `dispatch.py`, or `dispatch.mjs` and adapt it to
your platform. The provider passes each job to your program through environment variables.
Each example passes `ORB_TOKEN` into the orb to connect it to its thread. Most boot `ORB_IMAGE`;
the Boat example uses Boat's own base machine instead.

Amp's default `ORB_IMAGE` includes the Amp CLI and other dependencies. For ARM64 hosts, use the
[ARM64 image](#docker-image). You can also [bring your own image](#bring-your-own-image). See the
[dispatch contract](#dispatch-contract) for the full list of variables and what each job must do.

<a id="dispatch-examples"></a>

<div class="docs-tabs">
<input type="radio" name="dispatch-tabs" id="dispatch-tab-docker" checked />
<label for="dispatch-tab-docker">Docker</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-e2b" />
<label for="dispatch-tab-e2b">E2B</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-k8s" />
<label for="dispatch-tab-k8s">Kubernetes</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-apple" />
<label for="dispatch-tab-apple">Apple containers</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-modal" />
<label for="dispatch-tab-modal">Modal</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-vercel" />
<label for="dispatch-tab-vercel">Vercel</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-daytona" />
<label for="dispatch-tab-daytona">Daytona</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-exe" />
<label for="dispatch-tab-exe">exe.dev</label>
<input type="radio" name="dispatch-tabs" id="dispatch-tab-boat" />
<label for="dispatch-tab-boat">Boat</label>
<div class="docs-tab-panel" data-tab="docker">

<!-- prettier-ignore -->
```bash
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail

# "$ORB_IMAGE" is the Amp orb image.

case "$ORB_OPERATION" in
	spawn)
		image="$ORB_IMAGE"
		docker image inspect "${ORB_SNAPSHOT_ID-}" >/dev/null 2>&1 && image="$ORB_SNAPSHOT_ID"
		docker start "$ORB_ID" 2>/dev/null ||
			docker run --init --platform linux/amd64 -d --name "$ORB_ID" \
				"$image" sleep infinity
		docker exec -d --env ORB_TOKEN "$ORB_ID" amp orb bootstrap
		;;
	pause)
		docker stop "$ORB_ID"
		[ -z "${ORB_SNAPSHOT_ID-}" ] || docker commit "$ORB_ID" "$ORB_SNAPSHOT_ID"
		;;
	delete)
		docker rm -f "$ORB_ID"
		;;
esac
```

Docker does not save memory, so processes in the orb stop when it pauses. The container keeps its
files, so a waking orb runs `.agents/resume`, not `.agents/setup`. Project snapshots are local
images, so they work only on this host.

</div>
<div class="docs-tab-panel" data-tab="e2b">

```bash
# Needs the E2B CLI, `npm install e2b`, and E2B_API_KEY. Build the template once:
echo 'FROM us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb:latest' \
    > e2b.Dockerfile
e2b template create amp-orbs --dockerfile e2b.Dockerfile \
    --cpu-count 4 --memory-mb 8192 --ready-cmd true
export E2B_TEMPLATE=amp-orbs
```

<!-- prettier-ignore -->
```javascript
#!/usr/bin/env node
// dispatch.mjs
import { Sandbox } from 'e2b'

const {
	ORB_OPERATION, ORB_ID, ORB_TOKEN, AMP_URL, E2B_TEMPLATE,
} = process.env
const metadata = { orbId: ORB_ID }
const [existing] = await Sandbox.list({
	query: { metadata, state: ['running', 'paused'] },
}).nextItems()

if (ORB_OPERATION === 'spawn') {
	const timeoutMs = 60 * 60 * 1000
	const sandbox = existing
		? await Sandbox.connect(existing.sandboxId, { timeoutMs })
		: await Sandbox.create(E2B_TEMPLATE, {
			metadata, timeoutMs, lifecycle: { onTimeout: 'pause' },
		})
	const envs = { ORB_TOKEN }
	if (AMP_URL) envs.AMP_URL = AMP_URL
	const bootstrap = await sandbox.commands.run('amp orb bootstrap', {
		user: 'user', envs, background: true, timeoutMs: 0,
	})
	await bootstrap.disconnect()
} else if (ORB_OPERATION === 'pause' && existing) {
	await Sandbox.pause(existing.sandboxId)
} else if (ORB_OPERATION === 'delete' && existing) {
	await Sandbox.kill(existing.sandboxId)
}
```

E2B preserves the disk and memory on pause. This example also pauses on timeout
after an hour rather than deleting the orb. It finds running and paused orbs by
metadata, so the provider does not need to save sandbox IDs locally. It ignores
`ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
<div class="docs-tab-panel" data-tab="k8s">

Amp supplies a Helm chart for GKE, EKS, and AKS.

It runs the orb provider and a dispatch program that creates orbs as pods and saves paused orbs
to your storage bucket, so you do not write a dispatch program yourself. The bucket holds the
orb's files, not its memory, so processes in the orb stop when it pauses.

See [Kubernetes](/docs/orbs/self-hosted/kubernetes) to set it up.

</div>
<div class="docs-tab-panel" data-tab="apple">

<!-- prettier-ignore -->
```bash
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"

# "$ORB_IMAGE" is the Amp orb image.

ids=$(container list --all --quiet)
exists() { grep -Fxq "$ORB_ID" <<<"$ids"; }

case "$ORB_OPERATION" in
	spawn)
		image="$ORB_IMAGE"
		container image inspect "${ORB_SNAPSHOT_ID-}" >/dev/null 2>&1 && image="$ORB_SNAPSHOT_ID"
		if exists; then
			container start "$ORB_ID"
		else
			container run --detach --init --rosetta \
				--platform linux/amd64 --cpus 4 --memory 8G \
				--name "$ORB_ID" "$image" sleep infinity
		fi
		# A bare --env NAME copies the value from the host.
		container exec --detach --env ORB_TOKEN --env AMP_URL \
			"$ORB_ID" amp orb bootstrap
		;;
	pause)
		if exists; then
			container stop "$ORB_ID"
			[ -z "${ORB_SNAPSHOT_ID-}" ] || container commit "$ORB_ID" "$ORB_SNAPSHOT_ID"
		fi
		;;
	delete)
		if exists; then
			container delete --force "$ORB_ID"
		fi
		;;
	*)
		exit 1
		;;
esac
```

Apple containers do not save memory, so processes in the orb stop when it pauses. Saving project
snapshots with `container commit` needs `container` 1.6.0 or later. Snapshots are local images, so
they work only on this Mac.

</div>
<div class="docs-tab-panel" data-tab="modal">

<!-- prettier-ignore -->
```python
#!/usr/bin/env python3
# dispatch.py
import os

import modal

# Needs `pip install modal` plus MODAL_TOKEN_ID and MODAL_TOKEN_SECRET.
APP = "amp-orbs"
app = modal.App.lookup(APP, create_if_missing=True)
snapshots = modal.Dict.from_name("amp-orb-snapshots", create_if_missing=True)
orb_id = os.environ["ORB_ID"]

try:
    sandbox = modal.Sandbox.from_name(APP, orb_id)
except modal.exception.NotFoundError:
    sandbox = None

match os.environ["ORB_OPERATION"]:
    case "spawn":
        if sandbox is None:
            # Modal has no pause, so resume from the last snapshot.
            snapshot = snapshots.get(orb_id)
            if snapshot:
                image = modal.Image.from_id(snapshot)
            else:
                image = modal.Image.from_registry(os.environ["ORB_IMAGE"])
            sandbox = modal.Sandbox.create(
                "sleep", "infinity", app=app, name=orb_id, image=image,
                cpu=4, memory=(16384, 16384), timeout=24 * 60 * 60,
            )
        # Modal execs run as root; point bootstrap at the image user's home.
        bootstrap = "setsid amp orb bootstrap </dev/null >/dev/null 2>&1 &"
        sandbox.exec(
            "bash", "-c", bootstrap,
            env={
                "ORB_TOKEN": os.environ["ORB_TOKEN"],
                "AMP_URL": os.environ.get("AMP_URL"),
                "HOME": "/home/user", "USER": "user",
            },
        ).wait()
    case "pause":
        if sandbox:
            image_id = sandbox.snapshot_filesystem(timeout=120).object_id
            snapshots[orb_id] = image_id
            sandbox.terminate()
    case "delete":
        if sandbox:
            sandbox.terminate()
        snapshots.pop(orb_id, None)
```

Modal's filesystem snapshot keeps the orb's files but not its memory, so processes in the orb stop
when it pauses. This example ignores `ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
<div class="docs-tab-panel" data-tab="vercel">

<!-- prettier-ignore -->
```javascript
#!/usr/bin/env node
// dispatch.mjs
import { APIError, Sandbox, Snapshot } from '@vercel/sandbox'

const { ORB_OPERATION, ORB_ID, ORB_IMAGE, ORB_TOKEN, AMP_URL } = process.env
const credentials = {
	token: process.env.VERCEL_TOKEN,
	teamId: process.env.VERCEL_TEAM_ID,
	projectId: process.env.VERCEL_PROJECT_ID,
}

if (ORB_OPERATION === 'spawn') {
	const sandbox = await Sandbox.getOrCreate({
		...credentials,
		name: ORB_ID,
		// Copy Amp's image to VCR, then set ORB_IMAGE to its VCR URL.
		image: ORB_IMAGE,
		resources: { vcpus: 4 },
		timeout: 60 * 60 * 1000,
		persistent: true,
		snapshotExpiration: 0,
		keepLastSnapshots: { count: 1, expiration: 0, deleteEvicted: true },
	})
	const setup = await sandbox.runCommand({
		cmd: '/bin/bash',
		args: ['-c', 'hostname localhost && mkdir -p /vercel/sandbox'],
		sudo: true,
	})
	if (setup.exitCode !== 0) throw new Error(await setup.stderr())
	await sandbox.runCommand({
		cmd: '/usr/bin/setpriv',
		args: [
			'--reuid=user', '--regid=user', '--init-groups', '--',
			'/usr/bin/env', `ORB_TOKEN=${ORB_TOKEN}`,
			...(AMP_URL ? [`AMP_URL=${AMP_URL}`] : []),
			'HOME=/home/user', 'USER=user', '/bin/bash', '-lc',
			'exec amp orb bootstrap',
		],
		sudo: true,
		detached: true,
	})
} else if (ORB_OPERATION === 'pause' || ORB_OPERATION === 'delete') {
	try {
		const sandbox = await Sandbox.get({
			...credentials, name: ORB_ID, resume: false,
		})
		if (ORB_OPERATION === 'delete') {
			if (sandbox.status === 'running') await sandbox.stop()
			for await (const saved of await sandbox.listSnapshots()) {
				if (saved.status !== 'created') continue
				const snapshot = await Snapshot.get({
					...credentials, snapshotId: saved.id,
				})
				await snapshot.delete()
			}
			await sandbox.delete()
		} else if (sandbox.status === 'running') await sandbox.stop()
	} catch (error) {
		if (!(error instanceof APIError && error.response.status === 404)) {
			throw error
		}
	}
}
```

Vercel saves the orb's files when it stops, but not its memory, so processes in the orb stop when it
pauses. This example ignores `ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
<div class="docs-tab-panel" data-tab="daytona">

<!-- prettier-ignore -->
```python
#!/usr/bin/env python3
# dispatch.py
import os
from uuid import uuid4
from daytona import (
    Daytona, DaytonaNotFoundError, CreateSandboxFromImageParams,
    Resources, SessionExecuteRequest, SandboxState,
)

daytona = Daytona()
operation = os.environ['ORB_OPERATION']
try:
    sandbox = daytona.get(os.environ['ORB_ID'])
except DaytonaNotFoundError:
    sandbox = None

if operation == 'spawn':
    if sandbox is None:
        sandbox = daytona.create(CreateSandboxFromImageParams(
            name=os.environ['ORB_ID'], image=os.environ['ORB_IMAGE'],
            os_user='user', resources=Resources(cpu=4, memory=8, disk=10),
            auto_stop_interval=0, auto_delete_interval=-1,
        ), timeout=240)
    elif sandbox.state != SandboxState.STARTED:
        sandbox.start()
    env = {key: os.environ[key] for key in ('ORB_TOKEN', 'AMP_URL')
           if key in os.environ}
    sandbox.update_env(env, unset=['AMP_URL'])
    session = str(uuid4())
    sandbox.process.create_session(session)
    sandbox.process.execute_session_command(session, SessionExecuteRequest(
        command="bash -lc 'exec amp orb bootstrap'", run_async=True,
    ))
elif operation == 'pause' and sandbox:
    if sandbox.state == SandboxState.STARTED:
        sandbox.stop()
elif operation == 'delete' and sandbox:
    sandbox.delete()
```

Daytona keeps the orb's files when it stops, but not its memory, so processes in the orb stop when
it pauses. This example ignores `ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
<div class="docs-tab-panel" data-tab="exe">

<!-- prettier-ignore -->
```bash
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail

# Pause/resume currently requires access enabled by exe.dev support.
status=$(ssh exe.dev ls "$ORB_ID" --json |
    jq -r --arg id "$ORB_ID" '.vms[] | select(.vm_name == $id) | .status')

case "$ORB_OPERATION" in
    spawn)
        if [[ -z "$status" ]]; then
            ssh exe.dev new --name="$ORB_ID" --image="$ORB_IMAGE" \
                --cpu=2 --memory=8G --no-email
        elif [[ "$status" == paused ]]; then
            ssh exe.dev resume "$ORB_ID"
        fi
        {
            printf 'export ORB_TOKEN=%q\n' "$ORB_TOKEN"
            if [[ -n "${AMP_URL:-}" ]]; then
                printf 'export AMP_URL=%q\n' "$AMP_URL"
            fi
            cat <<'BOOTSTRAP'
sudo -u user --preserve-env=ORB_TOKEN,AMP_URL bash -lc \
    'cd "$HOME"; nohup amp orb bootstrap </dev/null >.amp-bootstrap.log 2>&1 &'
BOOTSTRAP
        } | ssh "$ORB_ID.exe.xyz" bash -s
        ;;
    pause)
        if [[ "$status" == running ]]; then
            ssh exe.dev pause "$ORB_ID"
        fi
        ;;
    delete)
        if [[ -n "$status" ]]; then
            ssh exe.dev rm "$ORB_ID"
        fi
        ;;
esac
```

exe.dev saves memory on pause, so processes keep running when the orb wakes. This example ignores
`ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
<div class="docs-tab-panel" data-tab="boat">

Install the [Boat CLI](https://docs.boat.dev/cli-reference) and sign in with `boat login`. The
controller needs Bash, `jq`, and OpenSSH. Enable [snapshots](https://docs.boat.dev/snapshots)
in Boat's settings and keep zero data retention off.

<!-- prettier-ignore -->
```bash
#!/usr/bin/env bash
# dispatch.sh
set -euo pipefail
export BOAT_NO_UPDATE=1
# Keep this directory on persistent storage.
STATE="${BOAT_ORB_STATE:-$HOME/.amp-boat-orbs}"
mkdir -p "$STATE"
trap 'echo "Boat $ORB_OPERATION $ORB_ID failed at line $LINENO" >&2' ERR
MAP="$STATE/$ORB_ID"
id=""
state=""
if [[ -s "$MAP" ]]; then
    id=$(cat "$MAP")
    state=$(boat info "$id" --json | jq -er '.sandbox.state')
fi
case "$ORB_OPERATION" in
    spawn)
        if [[ -z "$id" ]]; then
            # Read all JSON Lines before parsing. Increase the TTL for longer turns.
            out=$(boat new --json --ttl 3600 --no-env)
            printf '%s\n' "$out" | jq -ser 'map(select(.event == "ready"))[0].id // empty' > "$MAP"
            id=$(cat "$MAP")
        else
            while [[ "$state" == stopping ]]; do
                sleep 2
                state=$(boat info "$id" --json | jq -er '.sandbox.state')
            done
            if [[ "$state" == stopped || "$state" == error ]]; then
                boat resume "$id" >/dev/null
            fi
        fi
        {
            printf 'export ORB_TOKEN=%q AMP_URL=%q\n' "$ORB_TOKEN" "${AMP_URL:-https://ampcode.com}"
            printf '%s\n' \
                'export PATH="$HOME/.local/bin:$HOME/.amp/bin:$PATH"' \
                'command -v amp >/dev/null || curl -fsSL https://ampcode.com/install.sh | bash' \
                'nohup amp orb bootstrap </dev/null >~/.amp-bootstrap.log 2>&1 &'
        } | boat ssh "$id" bash -se -o pipefail
        ;;
    pause)
        if [[ -n "$id" && "$state" != stopped ]]; then
            [[ "$state" == stopping ]] || boat stop "$id" >/dev/null
            until [[ "$(boat info "$id" --json | jq -er '.sandbox.state')" == stopped ]]; do
                sleep 2
            done
        fi
        ;;
    delete)
        if [[ -n "$id" ]]; then
            boat delete "$id" --yes
            rm -f "$MAP"
        fi
        ;;
esac
```

Boat keeps home files for 14 days after a pause, but not `/tmp` or `~/.cache`, and memory may not
survive. This example uses Boat's base machine instead of `ORB_IMAGE` and ignores
`ORB_SNAPSHOT_ID`, so every new thread runs `.agents/setup`.

</div>
</div>

### Set Up a Provider

1. Open **Settings → Orbs** for your workspace, click **Add Orb Provider**, and give the provider a
   name such as `our-k8s-orbs`. Members pick the provider by this name when they start a thread.
   To add a provider only you can use, without a workspace admin, click **Add Provider** in your
   personal [Settings → Orbs](/settings/orbs) instead.

   <div class="docs-media-stage"><img class="docs-transparent-media docs-media-light" width=1120 style="aspect-ratio: 1120/753" src="https://static.ampcode.com/docs/self-hosted-orbs-add-provider-light.png?v=3" alt="The Add Orb Provider dialog with the name our-k8s-orbs"/><img class="docs-transparent-media docs-media-dark" width=1120 style="aspect-ratio: 1120/753" src="https://static.ampcode.com/docs/self-hosted-orbs-add-provider-dark.png?v=3" alt="The Add Orb Provider dialog with the name our-k8s-orbs"/></div>

2. Copy the `aop_live_…` token. It is shown once.
3. Make your dispatch program executable and run the orb provider with the token in its
   environment and the absolute path of your
   program, under a service manager that restarts it after a reboot:

   ```sh
   chmod +x dispatch.sh
   export AMP_ORB_PROVIDER_TOKEN="<copied token>"
   amp orb provider /opt/orbs/dispatch.sh
   ```

Run exactly one `amp orb provider` per token. Two of them, for example on two machines or a
forgotten terminal next to the service, each claim a share of the jobs and each start their own
orbs, so a thread's orb ends up on one while its next job goes to the other. A provider tells Amp
when it starts and stops, and Amp notices one that was killed. If another provider is still
connected when one starts, it asks for confirmation at a terminal; without a terminal it logs the
other provider and waits for it to stop, so a rollout that stops the old one first needs nothing
special. Pass `--allow-multiple` only if your dispatch program shares its state across all of them.

Select the provider in workspace settings to make it the default for new threads.

Self-hosted orbs are on by default. A workspace admin can turn them off with the **Self-Hosted
Orbs** setting in **Settings → Orbs**. While they are off, members cannot start orbs on the
workspace's providers and the providers stop receiving jobs; the providers and their tokens are
kept.

### Start Your First Thread

In the new thread composer, click **Orb**, open the type dropdown, and choose your provider
under **Self-Hosted**.

<div class="docs-media-stage"><img class="docs-transparent-media docs-media-light" width=1120 style="aspect-ratio: 1792/1500" src="https://static.ampcode.com/news/self-hosted-orbs-editor-light.avif" alt="The new thread composer with the orb picker open and our-k8s-orbs selected under Self-Hosted"/><img class="docs-transparent-media docs-media-dark" width=1120 style="aspect-ratio: 1792/1500" src="https://static.ampcode.com/news/self-hosted-orbs-editor-dark.avif" alt="The new thread composer with the orb picker open and our-k8s-orbs selected under Self-Hosted"/></div>

From the CLI, run:

```sh
amp -ox "your first message" --orb-provider our-k8s-orbs
```

Submit your prompt. Amp sends a request to your provider to spawn an orb, clones your repository,
runs `.agents/setup`, and starts the agent.

## Dispatch Contract

The orb provider runs your program once per job, with no arguments. The job arrives as environment
variables:

| Variable          | Value                                                                                                                   |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `ORB_OPERATION`   | `spawn`, `pause`, or `delete`.                                                                                          |
| `ORB_ID`          | The orb's ID, such as `amp-<thread id>`. Use it as the machine name so repeated jobs find the same machine.             |
| `ORB_IMAGE`       | `spawn` only. The image to boot: your `ORB_IMAGE` override or Amp's default.                                            |
| `ORB_SNAPSHOT_ID` | The [project snapshot](#project-snapshots) to boot from on `spawn`, or to save on `pause`. Unset when the job has none. |
| `ORB_TOKEN`       | `spawn` only. A short-lived credential that authorizes this orb's connection to its thread. Pass it into the orb.       |
| `AMP_URL`         | `spawn` only. The Amp server to connect to when it is not ampcode.com. Pass it into the orb with the token.             |

Each operation does the following:

| Operation | What your program does                                                                                                                                                                                                                                                                                                                                                                       |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spawn`   | Make the orb run. Start it again if it exists. Otherwise create it from `ORB_SNAPSHOT_ID` if you have that snapshot, or from `ORB_IMAGE`. Then start `amp orb bootstrap` inside it as the image's default user, detached, with `ORB_TOKEN` and `AMP_URL` set.                                                                                                                                |
| `pause`   | Stop the orb and keep its disk. When `ORB_SNAPSHOT_ID` is set, also save a copy of the disk under that ID for later `spawn` jobs. If your platform cannot keep the disk, delete the orb instead; the next spawn boots a fresh one and Amp sets the repository up again. If your platform also saves memory, processes keep running after the next `spawn`. Otherwise they stop with the orb. |
| `delete`  | Remove the orb and its disk.                                                                                                                                                                                                                                                                                                                                                                 |

Make every operation safe to repeat and finish within five minutes; your program's output goes
to the provider log, and spawn output also shows in the thread's setup log.

## Who Should Self-Host

We built self-hosted orbs for enterprises that can't use the orbs we host, because regulations might restrict where their code runs, because they need access to internal networks, or because they need custom hardware attached.

That's why the interface is so open: we want to support all the infrastructure our enterprise customers might have, as long as it meets the qualitative requirements.

The infrastructure needs to provide fast-to-start, resumable machines that scale to zero.

We only support our end of the dispatch contract, not every possible piece of infrastructure you can plug in.

We recommend you self-host orbs when:

- Your repository checkout and saved orb disks must stay in your cloud account.
- Your builds need private services that are reachable only from your network.
- Your rules require compute in a specific cloud, region, or account.
- You need custom hardware attached to orbs.

If you're on an Enterprise plan, let us know and we'll help you set it up.

## What Leaves Your Network

Amp runs the agent loop and model inference on its own servers. Your orb runs the agent's tools,
such as reading and editing files and running commands. The repository checkout, build output, and
saved orb disks stay on your infrastructure, and orbs can reach the private services your network
allows.

During a thread, the orb sends Amp what those tools produce: the file contents the agent reads,
command output, the diff shown in the thread's Changes tab, and screenshots and other files attached to
the thread. Amp stores this with the thread.

Amp never connects into your network and needs no cloud credentials or access to your cluster or
storage, so you do not need to allow [Amp's server IP addresses](/docs/egress-ips). Allow this
outbound access. Proxies must allow long-lived connections.

| Source                | Destination                         | Purpose                                       |
| --------------------- | ----------------------------------- | --------------------------------------------- |
| Orb provider          | `ampcode.com`, HTTPS 443            | Receiving jobs and reporting results          |
| Orb provider and orbs | `static.ampcode.com`, HTTPS 443     | Amp CLI updates                               |
| Orbs                  | `ampcode.com`, HTTPS and WSS 443    | Bootstrap and tool connections                |
| Orbs                  | `tunnel.ampcode.com`, WSS 443       | Amp Tunnel for portals, Terminal, and Desktop |
| Orbs                  | Your Git host, HTTPS 443            | Cloning and pushing the repository            |
| Orbs                  | `storage.googleapis.com`, HTTPS 443 | ripgrep download when the image has no `rg`   |

Portals, Terminal, and Desktop use Amp Tunnel. A portal on
a self-hosted orb can be opened only by a signed-in Amp user with access to the thread.

## Docker Image

Amp publishes two orb images. Both include the Amp CLI and the tools for portals, Terminal, and
Desktop. When the provider's environment does not set `ORB_IMAGE`, Amp passes the x64 image to
every `spawn` job.

| Image                                                                | Platform               |
| -------------------------------------------------------------------- | ---------------------- |
| `us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb:latest`       | Linux x64, the default |
| `us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb-arm64:latest` | Linux ARM64            |

For ARM64 hosts, such as Apple silicon Macs or ARM64 servers, set `ORB_IMAGE` to the ARM64 image
in the provider's environment:

```bash
export ORB_IMAGE=us-central1-docker.pkg.dev/amp-prod-0/amp-orb/amp-orb-arm64:latest
amp orb provider ./dispatch.sh
```

In the Docker example, replace `--platform linux/amd64` with `--platform linux/arm64`. Also add
`--shm-size=1g`, because the browser in the orb needs more than Docker's default 64 MB of shared
memory.

Changing `ORB_IMAGE` affects only new orbs. Existing orbs and project snapshots keep the image they
started from.

## Bring Your Own Image

<a id="use-your-own-image"></a>

Set `ORB_IMAGE` in the provider's environment to start orbs from your own image instead of the
default Amp orb image. Amp passes the value to every `spawn` job.

The easiest way is to build on the Amp orb image and add what every orb needs, such as language
toolchains or internal CA certificates. Keep project-specific setup in `.agents/setup`.

An image built from another base must run Linux x64 or ARM64 with glibc, so Alpine does not work.
It needs:

| Requirement                                                                                                                                            | Needed for                   |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
| Bash, coreutils, util-linux, and CA certificates                                                                                                       | Every orb                    |
| Git at `/usr/bin/git` or `/opt/git/bin/git`, 2.34 or later to sign commits                                                                             | Every orb                    |
| The Linux `amp` binary for the image's platform at `/usr/local/bin/amp`                                                                                | Every orb                    |
| A `user` user and group as the image's default user, with a writable `/home/user`                                                                      | Every orb                    |
| Passwordless sudo for `user` (`user ALL=(ALL) NOPASSWD: ALL`)                                                                                          | Every orb                    |
| Node.js 18 or later                                                                                                                                    | Portals and external agents  |
| tmux 2.9 or later                                                                                                                                      | Terminal and external agents |
| curl                                                                                                                                                   | External agents              |
| `/opt/amp-wayland` from the Amp orb image, xwayland, dbus, gsettings-desktop-schemas, xsettingsd, wlr-randr, pulseaudio, ffmpeg, and fonts-dejavu-core | Desktop                      |
| ripgrep, gh, and openssh-client                                                                                                                        | Recommended                  |

Amp uses passwordless sudo inside the orb to configure system Git settings and prepare runtime
files, as well as manage Amp services on images that use systemd.

## Project Snapshots

A project snapshot is a saved copy of a prepared orb, with the repository cloned and
`.agents/setup` already run, so later threads skip setup. Amp uses snapshots on self-hosted
providers the same way it does on Amp-hosted orbs, as long as your dispatch program supports
them. Of the examples above, Docker, Apple containers, and the Kubernetes chart do. See
[The Orb Lifecycle](/docs/orbs/customizing#the-orb-lifecycle) for when Amp saves and reuses them.

To save a snapshot, Amp sends a `pause` job with `ORB_SNAPSHOT_ID` set and then a `spawn` job
for the same orb. Store a copy of the orb under that ID on `pause`, and boot from the copy when a
later `spawn` names it.

If your dispatch program ignores `ORB_SNAPSHOT_ID`, every thread starts from the image and runs
`.agents/setup`.

To list or delete a project's snapshots, run:

```bash
amp projects snapshots list <project>
amp projects snapshots delete <project>
```

## Bring Your Own Repository Checkout

By default, Amp clones the project's repository into each new orb and sets up Git and `gh` with
short-lived GitHub tokens for the member who created the thread. If your orb image already
contains the repository and its own Git credentials, turn off **Repository Setup** in the
provider's **Edit** dialog in workspace settings.

<div class="docs-media-stage"><img class="docs-transparent-media docs-media-light" width=1120 style="aspect-ratio: 1120/780" src="https://static.ampcode.com/docs/self-hosted-orbs-edit-repository-setup-light.png" alt="The Edit Orb Provider dialog with Repository Setup turned off"/><img class="docs-transparent-media docs-media-dark" width=1120 style="aspect-ratio: 1120/780" src="https://static.ampcode.com/docs/self-hosted-orbs-edit-repository-setup-dark.png" alt="The Edit Orb Provider dialog with Repository Setup turned off"/></div>

Amp then does not clone or fetch the repository, change the orb's Git credential helpers or `gh`
login, or get a GitHub token for the orb. It still sets the commit author, runs the pre-setup
script and `.agents/setup`, and starts the agent. The setting applies to orbs Amp sets up after
you change it and does not apply to repositories hosted on Amp.

Your image must have a Git checkout of the project's repository at `/home/user/workspace/repo`
with `origin` and `origin/HEAD` set, so the Changes view and Ship can find the base branch.
Keeping the checkout up to date is your job. Pushes and pull requests use the credentials in the
orb, so use a credential helper that gets a fresh token each time Git asks; a token that lasts an
hour can expire during a long thread.

## Pricing

Self-hosted orbs are free for every workspace until October 29. After that, they stay free on the
Hobby, Individual, and Teams plans, and Enterprise workspaces pay 100% of the
[standard Amp-hosted orb price](/docs/orbs/sizes-and-costs) for the orb's CPUs and memory. Volume
discounts are available; [contact the Amp team](/contact-sales).

You also pay for the infrastructure that runs your orbs. Your thread usage shows each orb's runtime
and size, even when its cost is $0.

Prices are at the standard credit rate; an Enterprise workspace pays in its credits at its rate.
An orb is billed by the minute while it is connected to a thread, with the CPUs and memory it
reports. Only the thread's orb is billed; its agent tools sandbox is not.

## Troubleshoot

If an orb does not start, read the orb provider's log and the thread's setup log. If the cause
is not on your side, [send a bug report](/docs/support/reporting-bugs) from the thread and
include the log lines.
