---
sidebar_position: 7
title: sidecars & sidecarSpecs
---

# `sidecarSpecs` and `sidecars`

## What it is

Some agents need a long-lived service running beside them: a browser, a vector
database, a bridge that turns a REST API into tools. Those are **sidecars**.

`sidecarSpecs` carries the **whole specification** — name, a checksum-pinned
image on Zibby's own CDN, the port, the per-tenant config it reads — rather than
a name the platform must already know. That is what makes a brand-new
sidecar-backed agent *a template and nothing else*.

`sidecars` is the short form: a name for a sidecar the platform already ships.

At deploy the control plane fetches the image, **verifies its SHA-256**, loads it
and starts it on the internal network. The agent's run container never pulls
anything and never dials it directly.

:::caution Self-hosted only
Sidecar hosting is not built on Zibby Cloud yet. A template that declares one is
marked self-hosted-only and the cloud catalog will not deploy it. There is one
exception, [below](#the-one-cloud-exception).
:::

## Syntax

```js
spec: {
  selfHostOnly: true,
  sidecarSpecs: [{
    name: 'openapi-mcp',
    version: '0.2.3',
    s3Url: 'https://dl.zibby.app/sidecars/openapi-mcp/0.2.3.tar.gz',
    sha256: 'caa4ebfc04acf725f3afd0afa8d0ddf942a600eea364ec821351e3f19af53b4f',
    bytes: 2500336,                        // informational — shown in the dialog
    arch: {
      arm64: {
        s3Url:  'https://dl.zibby.app/sidecars/openapi-mcp/0.2.3-arm64.tar.gz',
        sha256: '38fe9ca08961eed8c2768e3d036da6deaf4ca2384543ea243deb9685a77ca566',
        bytes:  2305415,
      },
    },
    port: 8080,
    healthPath: '/health',
    warm: true,
    requestConfigKeys: ['OPENAPI_APIS', 'API_ROOT', 'API_TOKEN'],
  }],
  // NO `sidecars: ['openapi-mcp']` — a full spec implies its own name.
}
```

## Properties

| Name | Type | Required | Allowed values | Default | Update behavior |
|---|---|---|---|---|---|
| `name` | string | **Yes** | lowercase, starts with a letter, letters/digits/hyphens, 2–31 chars. May not shadow a built-in name | — | Re-applied |
| `port` | integer | **Yes** | 1–65535 | — | Re-applied |
| `sha256` | string | **Yes** | 64 hex characters | — | Re-applied — a new checksum pulls a new image |
| `s3Url` | string | **Yes** | an allow-listed host | — | Re-applied |
| `version` | string | No | a version string you own | none | Re-applied; shown in the Update dialog |
| `arch.<arch>` | object | No | `{ s3Url, sha256, bytes }`, validated identically | none | Re-applied |
| `healthPath` | string | No | a path | `/health` | Re-applied |
| `warm` | boolean | No | | `false` | Re-applied |
| `bytes` | integer | No | **informational only** — never checked against the download | none | Card only |
| `dataPath` | string | No | an absolute path — and it must be `/data` | none | Re-applied |
| `dataPurpose` | string | No | `data`, `credentials` | `data` | Re-applied — decides what uninstall deletes |
| `requestConfigKeys` | string[] | No | Env keys read **per request** from the declaring agent's own encrypted bag | `[]` | Re-applied |
| `envKeys` | string[] | No | container-wide environment | `[]` | Re-applied |
| `publicPaths` | string[] | No | prefixes exposed **anonymously** on your box's public origin — refused by default | none | Re-applied |
| `storeTypes` | string[] | No | store types this sidecar serves | `[]` | Re-applied |
| `instancePerAgent` | boolean | No | | `false` | Re-applied |
| `memoryBytes` / `pidsLimit` | integer | No | 64 MiB – 8 GiB / 16 – 4096 | platform defaults | Re-applied |

`sidecars` is simply `string[]`. A name nobody knows is skipped with a warning,
not an error.

## One shared service, many isolated tenants

A sidecar is **one process hosting N isolated tenants**, not one container per
agent. Everything that differs per agent — data *and* configuration — is
isolated:

- **Data** is keyed by a server-derived tenant key, so different tenants run in
  parallel and the same tenant serialises.
- **Configuration and secrets** come from `requestConfigKeys`, injected **per
  request** from the declaring agent's encrypted Env bag — never as
  container-wide environment.

That last point is the one people get wrong. Putting an OAuth client id and
secret in `envKeys` means two projects with different apps collide, and the fix
is not "error on conflict" — it is per-request injection.

## Gotchas

:::danger A full spec implies its name
If you declare `sidecarSpecs`, do **not** also list that name in `sidecars`.
The name is taken from the spec. Repeating it is a second place to keep in sync.
:::

:::danger Durable state lives at `/data` — one path, every sidecar
The `dataPath` you declare and the path your app actually writes must be the
**same string**. Nothing cross-checks them, so a mismatch is silent: the data
lands in the container's throwaway layer and dies with it, while the volume you
declared sits empty. A live OAuth token store was lost exactly that way. Do not
"fix" a mismatch by injecting a path variable — change one of them.
:::

**A checksum pins bytes, not a CPU.** There is deliberately no fallback between
architectures: an x86 image verifies perfectly on an ARM box and then dies with
`exec format error`. Publish and pin each architecture, or that architecture
fails loudly.

**A URL with no checksum fails closed.** The image is never loaded unverified.

**A template-carried sidecar does not auto-update.** Its version belongs to you:
it moves when your `version`/`sha256`/`s3Url` move and the user takes the
Update. Platform-shipped sidecars follow a CDN channel instead.

:::danger A sidecar with no authentication of its own must not declare `publicPaths`
`publicPaths` exposes those prefixes **anonymously** on your box's public
origin, with no platform authentication added. Agents and the chat assistant
reach a sidecar over the internal network and do not need it.

Declare an [`entryPoints.mcp`](./surfaces.md) with `auth: 'bearer-pat'` instead,
and let the platform's own authenticated endpoint forward to you — it adds the
token check, the ownership check, and per-request tenant configuration.

It also costs you cloud deployability. See below.
:::

**`warm: true` is sometimes required, not an optimisation.** A sidecar is
started by the boot pre-warmer, by a public-path proxy, or by the store router.
An agent that is dialled directly as an MCP server matches none of those, so
without `warm` it would never be running.

**Share a spec, do not copy it.** If several templates use the same sidecar,
import the spec from one place. Two inlined copies at different versions will
fight, and the last deploy wins — which can silently downgrade the shared engine.

## The one cloud exception

A self-hosted-only template **is** deployable on Zibby Cloud when it is
browser-host-servable — judged from the declaration, never from the name:

- every carried spec is fully pinned (`name`, `version`, `port`, `s3Url`,
  `sha256`), **and**
- no spec declares `publicPaths`, **and**
- the template declares an `entryPoints.mcp` pointing at one of its sidecars.

One `publicPaths` entry costs the template its cloud deployability.

## See also

- [`surfaces` and `entryPoints`](./surfaces.md)
- [`stores`](./stores.md) — a store type brings its own sidecar; do not name it
  twice.
- [Bring your own sidecar](../self-host/custom-sidecars.md) — the operator route,
  for a service that is not a catalog agent.
