> ## Documentation Index
> Fetch the complete documentation index at: https://openagentcore-codex-e2b-unified-pause.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Installation options and advanced deployments

The [default installation](./install.md) needs no options. Use this page to choose the sandbox backend at install time, run behind an existing reverse proxy, split Core and Web across hosts, run Core natively or install without internet access.

Pass options to the downloaded script:

```sh theme={null}
./install.sh --sandbox docker
```

With the one-line command, append them after `bash -s --`. The release downloader also accepts `--version TAG` to select a published release; otherwise it selects the latest stable release. It verifies the bundle's SHA-256 before extracting it and keeps the verified bundle for [repair](./operations.md#installation-version-policy).

The installer prints each stage, then a summary of addresses, sign-in details and next steps. Set `NO_COLOR=1` to disable colors. A failed step stops installation without a success message.

## Process settings

These flags seed the installation's `config.json` once. Their defaults, valid values and restart behavior are defined in the [configuration reference](../configuration.md#settings). After installation, edit that file and run `oac apply`; rerunning the installer only [repairs](./operations.md#installation-version-policy) the installation.

[//]: # "BEGIN install-flags: generated by scripts/config-reference.py"

| Flag | config.json field |
| - | - |
| `--core-only` or `--web-only` | `mode` |
| `--native-core` | `native_core` |
| `--public-url` | `public_url` |
| `--host` | `host` |
| `--core-port` | `ports.core` |
| `--web-port` | `ports.web` |
| `--core-url` | `web.core_url` |
| `--ingress` | `ingress` |
| [//]: # (END install-flags) | |

`--config FILE` seeds `config.json` from a JSON file instead of these setting flags; they cannot be combined. A `--config` document follows the schema defaults, so set `ingress: "managed"` and `host: "0.0.0.0"` in it for managed HTTPS.

## Installation actions

These options choose an installation location or perform initial setup; they are not saved in `config.json`.

| Option | Purpose |
| - | - |
| `--install-dir DIR` | Absolute installation directory; defaults to `~/.oac/core`. A new installation requires an empty or missing directory, or one holding an [installation that never started](./install.md#install) |
| `--sandbox docker\|microsandbox\|e2b\|none` | Select the initial [sandbox backend](#sandbox-backend), saved in Core's database; change it later in Web |
| `--accept-docker-risks` | Accept Docker's weaker isolation without an interactive prompt |
| `--e2b-api-key-file FILE` | With E2B: absolute path to a private key file, no group/other access and at most 4 KiB |
| `--e2b-template ID:BUILD` | With E2B: ready template build as `template-id:build-uuid` |
| `--e2b-api-url URL` | With E2B: compatible service HTTPS API origin; use together with `--e2b-domain` |
| `--e2b-domain DOMAIN` | With E2B: sandbox data-plane DNS suffix; use together with `--e2b-api-url` |
| `--core-key-file FILE` | With Web-only: absolute path to a private file containing the existing Core key, at least 32 characters |

Several installations can share a machine when they use distinct installation directories and ports. Only one installation with managed HTTPS can hold [ports 80 and 443](#ports) on an IP address; use distinct IP addresses or an external shared proxy for more. Each installation has its own database, Core key and nodes.

## Sandbox backend

An installation runs its sandboxes on exactly one backend:

| `--sandbox` | Sandboxes run on | You then |
| - | - | - |
| `microsandbox` (default) | microVMs on your nodes, which need KVM | [Add nodes](./nodes.md) in Web |
| `docker` | Docker containers on your nodes; weaker isolation | [Add nodes](./nodes.md) in Web |
| `e2b` | E2B's cloud, sized by your template build | Nothing: E2B needs no nodes |
| `none` | Nothing yet | Choose in Web under **System** → **Manage sandbox configuration** |

Docker and microsandbox start at the Standard size in Web's [`standard-sizes.json`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/apps/web/src/features/sandbox/standard-sizes.json). If Core refuses the choice, for example because E2B rejects the key, the installer prints Core's message and exits; the services keep running and you choose the backend in Web. To change the backend or size later, see [change the sandbox configuration](./nodes.md#change-the-sandbox-configuration).

**Docker** shares each node's kernel with its sandboxes, and its node service account is [root-equivalent](./nodes.md#what-the-installer-sets-up). Choose it only for trusted workloads or hosts without KVM. The installer asks for confirmation (default No); without a terminal, pass `--accept-docker-risks`.

**E2B** needs a public HTTPS URL that is not loopback, because E2B's sandboxes call Core from E2B's cloud, so pass `--public-url`. Prepare the template build with the [E2B guide](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/e2b/README.md), then:

```sh theme={null}
./install.sh --public-url https://core.example --sandbox e2b \
  --e2b-api-key-file "$HOME/.oac/e2b-api-key" --e2b-template '<template-id>:<build-uuid>'
```

## Listeners and access

The default combined Docker installation selects `--ingress managed` and `--host 0.0.0.0`. Its gateway publishes Web on `--web-port` (8080 by default), and [ports 80 and 443](#ports) once HTTPS is on. Core's `--core-port` stays on loopback and PostgreSQL stays private. `--host` accepts IPv4 or IPv6, without a port, scheme or zone. Use a concrete server IP in the browser, not a wildcard. Managed ingress needs a local Docker Unix socket.

`--ingress external` uses your own reverse proxy instead. It is the only choice for Core-only, Web-only and native Core installations, and their default. Core and Web then listen on `--host`, loopback by default. External non-loopback listeners require an HTTPS `public_url` and a [reverse proxy](#https-and-the-reverse-proxy), and Web's domain setup is unavailable: set `public_url` in `config.json` and run `oac apply`.

`--public-url` seeds a DNS-based HTTPS origin for unattended setup; with managed ingress, the certificate and connectivity checks must pass. The ingress mode is fixed for an installation.

### Ports

Before it verifies the bundle or loads images, the installer checks `--host` and every port the installation will listen on: Web's and Core's, PostgreSQL's with native Core, and 80 and 443 with managed ingress and `--public-url`.

* `--host` must be an address of this machine, or a wildcard such as `0.0.0.0`.
* A port set with `--web-port`, `--core-port` or in the `--config` file must be free, and so must a port that a loopback `--public-url` names, such as 8080 in `http://localhost:8080`. Otherwise the installer stops, names the port and prints the `ss` command that finds the program holding it.
* A Web or Core port you leave out moves to the first free port above its default, at most 20 above, and never to another port of the same installation. The installer writes the chosen port to `config.json` and names it in the summary, for example `Port 8080 was in use; Web uses 8081.`
* Managed ingress uses ports 80 and 443 only for HTTPS and never moves them. The gateway publishes them once `public_url` is set, from `--public-url` or [domain setup in Web](./install.md#configure-the-domain-and-https), and no other program on the host may use them. If either is in use at installation, free it, install without `--public-url` and set up the domain later, or install with `--ingress external` and use your own [reverse proxy](#https-and-the-reverse-proxy).

After installation, `oac apply` [checks the ports](../configuration.md#how-oac-apply-works) of a changed `host` or port, and 80 and 443 when `public_url` turns HTTPS on. Domain setup in Web and `oac domain` check, before they start, that the hostname resolves and that no other program holds port 80 or 443, and name the port that is in use.

## HTTPS and the reverse proxy

With external ingress, Core and Web share one public origin. Your reverse proxy terminates TLS and routes by path:

| Path | Goes to | Callers |
| - | - | - |
| `/v1`, `/v1/*` | Core, `127.0.0.1:8091` by default | Applications, with a Project API key |
| `/api/v1/*` | Core, `127.0.0.1:8091` | Nodes, sandboxes and self-hosted machines. Uses WebSockets |
| Everything else | Web, `127.0.0.1:8080` by default | Browsers, and node installers at `/node-install/*` |

The proxy must:

* **Preserve Host.** Web accepts only the host of its public URL.
* **Pass WebSocket upgrades** on `/api/v1`.
* **Not buffer or time out streams.** `/v1` streams Session events.
* **Accept large uploads.** Source files may reach 512 MiB; Core enforces the limits.

Run the proxy on the Core host while Core and Web listen on loopback, the default. `oac status` prints these routes with your addresses and ports.

**Caddy** obtains the certificate itself and passes Host and WebSockets by default:

```caddyfile theme={null}
core.example {
	@core path /v1 /v1/* /api/v1/*
	handle @core {
		reverse_proxy 127.0.0.1:8091
	}
	handle {
		reverse_proxy 127.0.0.1:8080
	}
}
```

**nginx**, for example in `/etc/nginx/conf.d/oac.conf` inside the `http` block:

```nginx theme={null}
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name core.example;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name core.example;
    ssl_certificate     /etc/letsencrypt/live/core.example/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/core.example/privkey.pem;

    client_max_body_size 0;          # Core enforces its own upload limits
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_buffering off;             # server-sent events on /v1
    proxy_request_buffering off;
    proxy_read_timeout 1h;           # long-lived WebSockets and streams
    proxy_send_timeout 1h;

    location = /v1    { proxy_pass http://127.0.0.1:8091; }
    location /v1/     { proxy_pass http://127.0.0.1:8091; }
    location /api/v1/ { proxy_pass http://127.0.0.1:8091; }
    location /        { proxy_pass http://127.0.0.1:8080; }
}
```

Then set `public_url` in `~/.oac/core/config.json` and run `~/.oac/core/oac apply`. Check the routing:

```sh theme={null}
curl -s -o /dev/null -w '%{http_code}\n' -H 'OpenAI-Beta: agents=v1' https://core.example/v1/agents
```

`401` means `/v1` reached Core, which asks for a key. `404` means it reached Web: fix the proxy, or application calls and every node connection will fail.

TLS verification stays on everywhere. With a private certificate authority, node hosts, self-hosted machines and the Runtime image must trust it.

### Try it locally with a quick tunnel

A [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) gives a trial installation with external ingress a temporary public HTTPS address. It forwards to one port, so put a local proxy with the same routes in front:

```caddyfile theme={null}
http://:8443 {
	bind 127.0.0.1
	@core path /v1 /v1/* /api/v1/*
	handle @core {
		reverse_proxy 127.0.0.1:8091
	}
	handle {
		reverse_proxy 127.0.0.1:8080
	}
}
```

1. Start the proxy: `caddy run --config Caddyfile`.
2. Start the tunnel: `cloudflared tunnel --url http://127.0.0.1:8443`. It prints an address such as `https://random-words.trycloudflare.com`.
3. Set that address as `public_url` in `~/.oac/core/config.json` and run `~/.oac/core/oac apply`.

The address changes whenever `cloudflared` restarts; nodes bound to the old address must then be added again. Throughput is low, so a node's first Runtime download (about 500 MB) can be slow; see [slow links](./nodes.md#rerun-expiry-and-slow-links).

## Modes

| Mode | Runs | Use it for |
| - | - | - |
| all (default) | PostgreSQL, Core and Web | Most installations |
| `--core-only` | PostgreSQL and Core | A Core whose Web runs elsewhere, or scripts only. No Add node command |
| `--web-only` | Web | A second host for the console, paired with an existing Core |

The mode and native Core are fixed once installed; to change them, install into a new directory.

A Web-only console forwards signed-in `/core/v1` requests to its Core with the Core key, so `--core-url` must reach Core's `/core/v1` directly. The public URL doesn't, because the proxy sends `/core/v1` to Web:

* **Web on the Core host:** use `--core-url http://127.0.0.1:8091`.
* **Web on another host:** give Core a second HTTPS name that sends every path to Core, such as `https://core-api.example`, and allow only the Web host's address.

### Split deployment

| Host | Install | Reverse proxy |
| - | - | - |
| Core | `./install.sh --core-only --public-url https://core.example` | `core.example`: `/v1`, `/v1/*` and `/api/v1/*` to Core; `/node-install/*` to the Web host with Host rewritten to `console.example`; nothing else. `core-api.example`: every path to Core, for the Web host's address only |
| Web | `./install.sh --web-only …`, below | `console.example`: every path to Web |

```sh theme={null}
./install.sh --web-only --install-dir "$HOME/.oac/web" \
  --public-url https://console.example \
  --core-url https://core-api.example \
  --core-key-file "$HOME/core.key"
```

Browsers and node installers use `console.example`. Applications, nodes, sandboxes and self-hosted machines call Core at `core.example`; self-hosted machines also download their installer there, which is why `core.example` forwards `/node-install/*` to Web. Missing node files redirect to the release; for disconnected nodes, install Web from the [offline bundle](#offline-hosts).

Caddy on the Core host, where `203.0.113.10` is the Web host's address:

```caddyfile theme={null}
core.example {
	@core path /v1 /v1/* /api/v1/*
	handle @core {
		reverse_proxy 127.0.0.1:8091
	}
	handle /node-install/* {
		reverse_proxy https://console.example {
			header_up Host console.example
		}
	}
	handle {
		respond 404
	}
}

core-api.example {
	@web remote_ip 203.0.113.10
	handle @web {
		reverse_proxy 127.0.0.1:8091
	}
	handle {
		respond 403
	}
}
```

nginx on the Core host, with the `map` from the [main example](#https-and-the-reverse-proxy):

```nginx theme={null}
server {
    listen 443 ssl;
    server_name core.example;
    ssl_certificate     /etc/letsencrypt/live/core.example/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/core.example/privkey.pem;

    client_max_body_size 0;
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;

    location = /v1    { proxy_pass http://127.0.0.1:8091; }
    location /v1/     { proxy_pass http://127.0.0.1:8091; }
    location /api/v1/ { proxy_pass http://127.0.0.1:8091; }
    location /node-install/ {
        proxy_pass https://console.example;
        proxy_set_header Host console.example;   # Web serves node files only for its own name
        proxy_ssl_server_name on;
        proxy_ssl_name console.example;
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 2;
        proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
    }
    location / { return 404; }
}

server {
    listen 443 ssl;
    server_name core-api.example;
    ssl_certificate     /etc/letsencrypt/live/core-api.example/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/core-api.example/privkey.pem;

    allow 203.0.113.10;                        # the Web host
    deny all;
    client_max_body_size 0;
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_read_timeout 1h;
    location / { proxy_pass http://127.0.0.1:8091; }
}
```

The Web host's proxy sends every path to Web, for example `console.example { reverse_proxy 127.0.0.1:8080 }` in Caddy.

Copy `secrets/core.key` from the Core host with mode `0600`, then delete `$HOME/core.key`; the installer keeps its own copy. After a [Core key rotation](./operations.md#rotate-the-core-key), copy it again. A Web-only install selects no sandbox backend.

## Native Core

`--native-core` runs Core as a systemd user service; PostgreSQL and Web stay in containers. It needs a running systemd user manager with lingering, which the host administrator enables with `sudo loginctl enable-linger "$USER"`, and the bundle's native binaries must load on the host. PostgreSQL then listens on a loopback port the installer picks (`ports.database`). Native Core is unrelated to the sandbox backend.

## Offline hosts

Transfer the release's `*-linux-amd64-offline.tar.gz` and its `.sha256` file, verify and extract them, then run the bundled `./install.sh`. The offline bundle also carries the node and Runtime files, so Web serves them to nodes without release access.
