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

# Sizing & Resource Requirements

> CPU, memory, and storage requirements for self-hosted xpander deployments, with a calculator to size your cluster

This page tells you how much compute a self-hosted xpander deployment needs. The recommendations are derived from measured usage of the same services running in xpander cloud production, plus the Helm chart's configured requests and limits.

<Warning>
  xpander container images are **amd64 only**. Do not use ARM/Graviton instances (`t4g`, `m7g`, `c7g`, etc.).
</Warning>

***

## Example Estimates

Agents are autonomous - tasks are triggered from many sources, not only a user chatting. In xpander cloud production over the last 30 days, \~47% of executions were scheduled tasks, 23% webhooks, 19% SDK, 6% API, and only \~5% interactive chat. Size for the number of tasks running **concurrently at peak**, whatever triggers them.

| Concurrent tasks | Agents | Cluster capacity  | Example nodes                                       | Persistent storage |
| ---------------- | ------ | ----------------- | --------------------------------------------------- | ------------------ |
| 3                | 10     | 10 vCPU / 19 GiB  | 3 × `m5.xlarge` (or 2 × `t3.xlarge` for evaluation) | \~110 GiB          |
| 10               | 25     | 19 vCPU / 34 GiB  | 3 × `m5.2xlarge`                                    | \~180 GiB          |
| 20               | 50     | 29 vCPU / 49 GiB  | 4 × `m5.2xlarge`                                    | \~310 GiB          |
| 50               | 100    | 66 vCPU / 105 GiB | 9 × `m5.2xlarge`, enable autoscaling                | \~570 GiB          |

Capacity figures include \~50% headroom over pod resource requests for burst load, Kubernetes system pods, and node upgrades. Use the calculator below to size for your exact numbers.

***

## Sizing Calculator

Estimate cluster resources from your expected usage. The formula behind it is described in [How the estimate works](#how-the-estimate-works).

<iframe
  srcDoc={`<!doctype html><html><head><meta charset="utf-8"/><style>
:root {
  color-scheme: light dark;
  --xp-purple: #753CFF;
  --xp-purple-light: #9B70FF;
  --xp-violet-400: #a78bfa;
  --xp-mono: "JetBrains Mono", "Fira Code", "SF Mono", ui-monospace, Menlo, monospace;
  --xp-sans: "Helvetica Thin", Helvetica, Arial, sans-serif;
  --xp-bg: #ffffff;
  --xp-bg-card: #fafafa;
  --xp-bg-input: #ffffff;
  --xp-text: #18181b;
  --xp-text-muted: #71717a;
  --xp-text-faint: #a1a1aa;
  --xp-border: rgba(0,0,0,0.08);
  --xp-accent-bg: rgba(117,60,255,0.04);
  --xp-accent-border: rgba(117,60,255,0.25);
}
@media (prefers-color-scheme: dark) {
  :root {
    --xp-bg: #111113;
    --xp-bg-card: rgba(255,255,255,0.03);
    --xp-bg-input: rgba(255,255,255,0.02);
    --xp-text: #f4f4f5;
    --xp-text-muted: #a1a1aa;
    --xp-text-faint: #71717a;
    --xp-border: rgba(255,255,255,0.08);
    --xp-accent-bg: rgba(167,139,250,0.06);
    --xp-accent-border: rgba(167,139,250,0.35);
  }
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--xp-bg); color: var(--xp-text); font-family: var(--xp-sans); font-weight: 100; overflow-x: hidden; }
body { padding: 20px; font-size: 15px; line-height: 1.7; word-break: break-word; }
.kicker { font-family: var(--xp-mono); font-size: 11px; font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase; color: var(--xp-text-faint); margin: 0 0 10px; }
.sentence { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; background: var(--xp-bg-card); border: 1px solid var(--xp-border); border-radius: 12px; padding: 18px 22px; line-height: 2; }
.sentence .w { white-space: nowrap; }
input, select {
  background: var(--xp-bg-input);
  color: var(--xp-purple);
  border: 1px solid var(--xp-border);
  border-radius: 8px;
  padding: 6px 10px;
  font-family: var(--xp-mono);
  font-size: 13px;
  outline: none;
  transition: border-color 0.15s ease;
}
@media (prefers-color-scheme: dark) { input, select { color: var(--xp-violet-400); } }
input:hover, select:hover { border-color: var(--xp-accent-border); }
input:focus, select:focus { border-color: var(--xp-purple); }
input.num { width: 64px; text-align: center; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-top: 14px; }
.tile { background: var(--xp-bg-card); border: 1px solid var(--xp-border); border-radius: 12px; padding: 12px 16px; }
.tile .label { font-family: var(--xp-mono); font-size: 10px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; color: var(--xp-text-faint); margin: 0 0 4px; }
.tile .val { font-family: var(--xp-mono); font-size: 16px; color: var(--xp-purple); line-height: 1.4; }
.tile .sub { font-size: 12px; color: var(--xp-text-muted); margin-top: 2px; }
@media (prefers-color-scheme: dark) { .tile .val { color: var(--xp-violet-400); } }
.presets { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.presets button {
  background: transparent; color: var(--xp-text-muted);
  border: 1px solid var(--xp-border); border-radius: 999px;
  padding: 6px 14px; font-family: var(--xp-sans); font-size: 12px; font-weight: 300;
  cursor: pointer; transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.presets button:hover { border-color: var(--xp-accent-border); color: var(--xp-purple); background: var(--xp-accent-bg); }
@media (prefers-color-scheme: dark) { .presets button:hover { color: var(--xp-violet-400); } }
details { margin-top: 12px; }
summary { font-size: 12px; color: var(--xp-text-muted); cursor: pointer; }
.adv { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; padding: 10px 2px 0; font-size: 13px; color: var(--xp-text-muted); }
@media (max-width: 520px) { body { padding: 16px; font-size: 14px; } }
</style></head><body>
<p class="kicker">Size your cluster</p>
<div class="sentence">
<span class="w">At peak we run</span>
<input class="num" id="tasks" type="number" min="1" value="5" />
<span class="w">concurrent agent tasks across</span>
<input class="num" id="agents" type="number" min="1" value="15" />
<span class="w">agents, and</span>
<input class="num" id="wsshare" type="number" min="0" max="100" value="50" />
<span class="w">% of tasks use a workspace.</span>
</div>
<details>
<summary>Advanced assumptions</summary>
<div class="adv">
  <span>Concurrent tasks per worker replica: <input class="num" id="slots" type="number" min="1" max="20" value="4" /></span>
  <span>(default 4, raise via <span style="font-family:var(--xp-mono);font-size:12px">MAX_CONCURRENT_EXECUTIONS</span>)</span>
</div>
<div class="adv">
  <span>Not sure? Assume one concurrent task per agent. Most production teams peak at just a few concurrent tasks.</span>
</div>
</details>
<div class="grid">
<div class="tile"><p class="label">Pod requests</p><div class="val" id="req">-</div><div class="sub" id="reqsub">-</div></div>
<div class="tile"><p class="label">Recommended cluster</p><div class="val" id="cap">-</div><div class="sub">includes ~50% headroom</div></div>
<div class="tile"><p class="label">Example nodes</p><div class="val" id="nodes">-</div><div class="sub">amd64 / x86_64 only</div></div>
<div class="tile"><p class="label">Persistent storage</p><div class="val" id="disk">-</div><div class="sub">volumes + workspace data</div></div>
</div>
<div class="presets">
<button data-t="3" data-a="10">3 tasks / 10 agents</button>
<button data-t="10" data-a="25">10 tasks / 25 agents</button>
<button data-t="20" data-a="50">20 tasks / 50 agents</button>
<button data-t="50" data-a="100">50 tasks / 100 agents</button>
</div>
<script>
const el = id => document.getElementById(id);
function num(id, lo, hi, fb){ const v = parseInt(el(id).value, 10); return Number.isInteger(v) ? Math.min(hi, Math.max(lo, v)) : fb; }
function update(){
  const tasks = num("tasks", 1, 100000, 5);
  const agents = num("agents", 1, 100000, 15);
  const wsShare = num("wsshare", 0, 100, 50) / 100;
  const slots = num("slots", 1, 20, 4);
  const workers = Math.ceil(tasks / slots);
  const controllers = tasks <= 8 ? 1 : tasks <= 20 ? 2 : 3;
  const workspaces = Math.min(agents, Math.max(1, Math.round(tasks * wsShare)));
  const baseCpu = 2.05, baseMem = 6;
  const cpu = baseCpu + controllers * 1 + workers * 2 + workspaces * 0.5;
  const mem = baseMem + controllers * 2 + workers * 2.5 + workspaces * 1;
  const capCpu = Math.ceil(cpu * 1.5);
  const capMem = Math.ceil(mem * 1.5);
  const node = capCpu <= 16 ? { name: "m5.xlarge", cpu: 4, mem: 16 } : { name: "m5.2xlarge", cpu: 8, mem: 32 };
  const count = Math.max(2, Math.ceil(capCpu / node.cpu), Math.ceil(capMem / node.mem));
  const disk = Math.ceil((48 + agents * 5 + count * 2) / 10) * 10;
  el("req").textContent = cpu.toFixed(1) + " vCPU / " + Math.ceil(mem) + " GiB";
  el("reqsub").textContent = workers + " worker" + (workers > 1 ? "s" : "") + " · " + controllers + " controller" + (controllers > 1 ? "s" : "") + " · ~" + workspaces + " active workspace" + (workspaces > 1 ? "s" : "");
  el("cap").textContent = capCpu + " vCPU / " + capMem + " GiB";
  el("nodes").textContent = count + " \\u00d7 " + node.name;
  el("disk").textContent = "~" + disk + " GiB";
}
["tasks","agents","wsshare","slots"].forEach(id => { el(id).addEventListener("input", update); el(id).addEventListener("change", update); });
document.querySelectorAll(".presets button").forEach(b => b.addEventListener("click", () => { el("tasks").value = b.dataset.t; el("agents").value = b.dataset.a; update(); }));
update();
</script></body></html>`}
  style={{ width: "100%", height: "430px", border: "none", borderRadius: "12px", display: "block", colorScheme: "light dark" }}
/>

### How the estimate works

The calculator applies the same model we use to size xpander cloud:

1. **Concurrent tasks** is the primary driver. A task is any agent execution, from any source: chat, API/SDK calls, schedules, webhooks, MCP clients, Slack, or another agent. If you don't know your peak concurrency yet, **assume one concurrent task per agent** - that covers most production organizations we measured. In practice most teams peak at just a few concurrent tasks (typically under 5), and even the busiest organization peaks around 30.
2. **Agent Workers** = concurrent tasks ÷ 4 (each worker replica processes 4 tasks by default, configurable with `MAX_CONCURRENT_EXECUTIONS`). Each replica requests 2 vCPU / 2.5 GiB.
3. **Agent Controllers**: 1 replica up to \~8 concurrent tasks, 2 up to \~20, 3 beyond. Each requests 1 vCPU / 2 GiB.
4. **Active workspaces** = concurrent tasks × workspace share, capped at the agent count. Budget \~0.5 vCPU / 1 GiB per active workspace (measured typical load; each can burst to its 2 vCPU / 3 GiB limit).
5. **Fixed base** (\~2 vCPU / 6 GiB): AI Gateway, MCP, Chat, Code Runner, AWS Operator, API, Redis, PostgreSQL, container registry, metrics-server at one replica each.
6. **Cluster capacity** = requests × 1.5 for burst, DaemonSets, and safe node drains. **Storage** = 48 GiB of chart volumes + 5 GiB per agent (each agent that uses a workspace keeps a persistent volume) + image cache per node.

***

## Platform Services

Per-replica values. **Defaults** are what the Helm chart applies out of the box; **recommended** raises the values we found low against measured production usage.

| Service          | CPU request     | Memory request       | CPU limit     | Memory limit    | Notes                                                                                                 |
| ---------------- | --------------- | -------------------- | ------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| Agent Controller | 1000m           | 1 Gi → **2 Gi**      | 3000m         | 2 Gi → **3 Gi** | Orchestrator; observed steady-state memory is \~1.5-2 GiB, so raise the default before production use |
| Agent Worker     | 2000m           | 2.25 Gi → **2.5 Gi** | 4000m         | 3 Gi            | Scale replicas for task concurrency, not size                                                         |
| API              | 100m → **250m** | 256 Mi → **1 Gi**    | - → **2000m** | - → **1.5 Gi**  | Chart sets no limits; observed usage \~0.9-1.4 GiB                                                    |
| MCP              | 200m            | 750 Mi               | 400m          | 1 Gi            |                                                                                                       |
| Chat             | 300m            | 1 Gi                 | 600m          | 1.5 Gi          |                                                                                                       |
| AI Gateway       | 200m            | 400 Mi               | 400m          | 600 Mi          |                                                                                                       |
| Code Runner      | 400m            | 800 Mi               | 800m          | 1229 Mi         | Runs privileged (Docker-in-Docker); each concurrent code execution adds up to 1 vCPU / 2 GiB          |
| AWS Operator     | 200m            | 400 Mi               | 400m          | 600 Mi          | Optional component                                                                                    |

Override any of these in your Helm values (keys are camelCase):

```yaml theme={"dark"}
resources:
  agentController:
    requests:
      cpu: "1000m"
      memory: "2Gi"
    limits:
      cpu: "3000m"
      memory: "3Gi"
```

<Tip>
  Horizontal Pod Autoscaling is available for Agent Controller, AI Gateway, Agent Worker, and MCP via the chart's `autoscaling` values. The chart installs metrics-server by default to support it.
</Tip>

***

## Agent Workspaces

Each agent that uses workspace tools gets a dedicated pod, created on demand and reclaimed when idle. Size the cluster for the number of **concurrently active** workspaces, not total agents.

| Resource         | Value                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------- |
| CPU              | 250m request, 2000m limit                                                                    |
| Memory           | 256 Mi request, 3 Gi limit                                                                   |
| Storage          | 5 GiB persistent volume per agent                                                            |
| Idle shutdown    | 5 minutes without activity (configurable via `SANDBOX_IDLE_TIMEOUT` on the Agent Controller) |
| Maximum lifetime | 3 hours, then the pod is recycled                                                            |

Idle workspaces consume almost nothing (\~50 Mi observed); an active workspace typically uses \~0.5-1 vCPU and up to 1 GiB, bursting to its limits for heavy work. The workspace volume **persists after the pod is reclaimed**, so a returning agent keeps its files - plan 5 GiB of storage per workspace-using agent, not per pod.

<Info>
  The workspace image is \~1 GiB. The first workspace start on each node pulls it; subsequent starts on that node are fast. Budget \~2 GiB of node disk for image cache.
</Info>

***

## Data Services & Storage

The chart deploys single-replica Redis and PostgreSQL StatefulSets and a container registry alongside the platform:

| Component          | Persistent volume (default) | Guidance                                                                                                                                 |
| ------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Redis              | 8 GiB                       | Queues, caches, and runtime state. 2-4 GiB of memory is sufficient for a single organization; give the pod a 512 Mi-1 GiB memory request |
| PostgreSQL         | 20 GiB                      | Platform state; grows slowly                                                                                                             |
| Container registry | 20 GiB                      | Stores agent container images built in your cluster                                                                                      |

**Baseline storage: 48 GiB** of persistent volumes, plus 5 GiB per workspace-using agent (see above). All volumes use your cluster's default StorageClass unless overridden.

***

## Production Checklist

* **Node types**: x86\_64 only. General-purpose instances with a 1:4 vCPU:GiB ratio (`m5`, `m6i`) fit the workload profile; `t3` burstable instances are fine for evaluation, not for production.
* **Raise the low defaults**: Agent Controller and API memory (table above) before real load.
* **Enable autoscaling** for Agent Worker once concurrency grows beyond a couple of replicas.
* **Keep headroom**: pod requests should stay under \~65% of cluster capacity so node upgrades and bursts don't evict workloads.
* **Storage class**: SSD-backed volumes for Redis and PostgreSQL.

Next: follow the [deployment guide](/self-hosted/deployment) to install the chart, or the [EKS setup guide](/self-hosted/eks-setup) if you're starting from scratch on AWS.
