---
sidebar_position: 5
title: Custom (BYO) sidecars
---

# Bring your own sidecar

A **sidecar** is a long-lived service container the box runs next to your
agents — the platform's own vector-KB engine and OAuth token broker ship this
way. On a self-hosted box you can also register **your own** sidecar image.
That is the standard way to add a custom resident service to the platform —
most commonly a **thin MCP server that wraps an internal REST API**, so your
agents and the chat Copilot can call systems that only exist inside your
network.

No marketplace entry, no platform code change, no third-party registry: you
push a `docker save` tarball to your own box, the box pins its sha256, and the
sidecar flows through the exact same launch/health/idle-reap machinery as the
built-ins.

:::tip Wrapping a REST API? Try the OpenAPI bridge agent first.

If all you need is "my agents should be able to call this internal REST API",
you probably do **not** need to build an image at all. Deploy the **OpenAPI MCP
Bridge** agent from the marketplace, put your API's spec URL in its **Env** tab,
and every operation in the spec becomes an MCP tool — no code, no upload.

```json
OPENAPI_APIS = {"billing":{"specUrl":"https://internal/v2/api-docs","root":"https://internal"}}
```

It is a marketplace agent that carries its own sidecar, so deploying it is the
whole installation. Build your own image (below) when you need logic the bridge
can't express — a non-HTTP protocol, a stateful session, custom auth.
:::

## Push one

```bash
# 1. Build your image however you like; it must serve HTTP on one port.
docker build -t report-mcp:0.1.0 .
docker save report-mcp:0.1.0 | gzip > report-mcp.tar.gz

# 2. Push it to the box. Needs an OPERATOR access token
#    (a restricted per-user token is refused).
zibby sidecar push report-mcp.tar.gz \
  --name report-mcp --port 8080 \
  --health-path /health \
  --warm \
  --api-url https://your-box.example.com --token $OPERATOR_PAT
```

`push` streams the tarball up (`POST /selfhost/sidecars/artifacts`), then
registers the declaration (`PUT /selfhost/sidecars/report-mcp`) pinned to the
sha256 the server computed from the uploaded bytes. The image is verified and
`docker load`ed **before** anything is persisted — a corrupt or tampered
tarball can never become a registered sidecar.

Manage what's registered:

```bash
zibby sidecar list             # custom sidecars + the reserved built-in names
zibby sidecar remove <name>    # unregister + stop (image reclaimed; data volume kept) (data volumes are kept)
```

## Reach it

- **From the control plane / the chat Copilot** — the container listens on the
  box's infra network at `http://zibby-sidecar-<name>:<port>`. Agent run
  containers deliberately **cannot** dial it directly (they are isolated on the
  run network); access is brokered by the platform.
- **From a browser / third-party client** — every `--public-path` prefix is
  served at `https://<box-origin>/sidecars/<name>/<path>` through the
  control-plane reverse proxy. Anything not declared public stays
  infra-network-only.

:::warning `--public-path` is opt-in, and most sidecars don't need it
A declared public path is reachable **anonymously** — the platform adds no auth
and no identity; your app owns that surface entirely. Agents and the chat
Copilot reach a sidecar over the infra network, so an MCP server for them needs
**no** public path. It exists for browser flows (an OAuth callback a provider
redirects to). Registering one is therefore refused unless the box opts in with
`SIDECAR_BYO_PUBLIC_PATHS=1` in its `.env` (then restart the control-plane).
:::

### Wire it into the Copilot

For the "internal API → MCP → chat" use case, register the sidecar and then
attach its MCP endpoint to the Copilot (in chat):

> connect yourself to the MCP at `http://zibby-sidecar-report-mcp:8080/mcp`

The Copilot can also drive the whole flow itself with the owner-only tools
`zibby_add_sidecar` (takes a downloadable `url` + pinned `sha256` instead of a
file upload), `zibby_list_sidecars`, and `zibby_remove_sidecar`.

## Declaration reference

| Flag | Meaning |
|---|---|
| `--name` | `[a-z][a-z0-9-]{1,30}`. Built-in names (`gbrain`, `pingcode`, …) are reserved. |
| `--port` | The HTTP port your app listens on inside the container. Required. |
| `--version` | Display/tag version (default: first 12 chars of the sha256). |
| `--health-path` | GET path returning 2xx when ready (default `/health`). |
| `--public-path` | Repeatable. Path prefix exposed through the public reverse proxy — **anonymously**. Disabled unless the box sets `SIDECAR_BYO_PUBLIC_PATHS=1`; see below. |
| `--warm` | Keep it always-on (pre-warmed at boot, never idle-reaped). Default: on-demand launch + idle reap. |
| `--env-key` | Repeatable. A box `.env` variable forwarded into the container when set. |
| `--request-config-key` | Repeatable. Per-agent config: resolved from the **calling agent's encrypted Env bag** and sent per request — one shared container can serve N projects with different upstream credentials. |
| `--data-path` | Container path persisted on a named volume (survives restarts; kept on remove). |
| `--memory-bytes` | Container memory cap. |

## What the box guarantees

- **sha256-pinned, always.** The tarball's hash is computed server-side at
  upload and re-verified on every subsequent load — including the re-fetch
  path: the verified tarball is kept in the box's own object store, so if the
  docker image is ever pruned it is restored automatically without a re-upload.
- **Operator-only surface.** Upload/register/remove require the box
  owner/admin token; restricted per-user tokens get `403`.
- **Isolation unchanged.** Custom sidecars get the same placement as built-in
  ones: infra network, brokered access, no host mounts — the declaration
  cannot ask for privileged options.
- **No shadowing, at BOTH layers.** A custom sidecar can never take over a
  built-in sidecar's *name*, and its package can never take over a platform or
  another sidecar's *image tag*: the archive's manifest is read **before**
  `docker load` runs, and a package declaring `zibby-*` or another sidecar's
  image is refused outright — even if that image isn't on the box yet.
- **Uninstall reclaims storage.** Removing a sidecar deletes its image; a
  superseded image is reclaimed when you push a new version. The declared data
  volume is **kept** (it holds your content) — the response tells you its name,
  and `zibby sidecar remove <name> --purge-data` deletes it deliberately.

:::note Cloud
BYO sidecars are **self-host only** today. On cloud, wrap an external API as a
remote MCP server and attach it with `zibby_add_mcp` instead.
:::
