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

# Build a custom image

> Write a Dockerfile, build the image on Agent37's infrastructure or import it from a public registry, and run instances from the pinned template.

You can run an Agent37 instance from any Docker image. Register it as a [workspace template](/agents-api/templates) in one of two ways:

| Your image                       | Register with                           | What Agent37 needs                                                                                       |
| -------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Not built yet, local, or private | A [cloud build](#build-it-in-the-cloud) | A folder with the `Dockerfile` and the files it `COPY`s. Agent37 runs the build; no local Docker needed. |
| In a public registry             | `image_ref`                             | A fully qualified reference that Agent37 can pull anonymously during registration.                       |

A cloud build fetches everything itself — the `FROM` base image and anything `RUN` downloads — so those sources must be publicly reachable. There is no build-secret or private-registry-credential support yet; if your base image is private, [contact support](mailto:support@agent37.com).

A complete example — a `Dockerfile`, an example skill, a registration script, and a tiny LLM proxy — lives in [`custom-agent-image/`](https://github.com/agent37-platform/starter-kit/tree/main/examples/custom-agent-image), ready to copy into your own repo.

This page is the walkthrough; [Templates → build on the Hermes base image](/agents-api/templates#build-on-the-hermes-base-image) is the reference for the contract.

<Note>
  When you register a template, Agent37 copies your image once into private storage and pins it by digest. That snapshot is what every instance runs, so registering takes up to a few minutes for a large image. Re-pushing the same tag later does nothing: publish a new tag and [update the template](/agents-api/templates#update-a-template), or re-run the [cloud build](#build-it-in-the-cloud). Your image is stored privately and never republished.
</Note>

## 1. Choose a starting image

To customize Hermes, build on [`ghcr.io/agent37-platform/hermes-base`](https://github.com/orgs/agent37-platform/packages/container/package/hermes-base). It includes Hermes, the gateway, Chromium, and the standard toolchain. This example adds a CLI and a skill:

```dockerfile theme={null}
FROM ghcr.io/agent37-platform/hermes-base:latest

USER root
RUN apt-get update && apt-get install -y --no-install-recommends your-cli \
 && rm -rf /var/lib/apt/lists/*
COPY your-skill/ /usr/local/share/agent37/default-skills/your-skill/
USER node
```

Bake binaries into `/usr/local/bin`, skills into `/usr/local/share/agent37/default-skills/` (the entrypoint copies them to `~/.hermes/skills` at boot), and everything else into `/usr/local` or `/opt` — never `/home/node` or `/home/linuxbrew`, which are masked at runtime. Keep the base `ENTRYPOINT`. See [the full contract](/agents-api/templates#build-on-the-hermes-base-image).

You can also use an existing image or start from any base. Its main process must keep running. If it serves HTTP, bind it to `0.0.0.0` and note its listening port; you will pass that as `default_port` when you register the template. An image with no HTTP service can omit `default_port` and be driven through [exec](/agents-api/exec), but it still needs a long-running `ENTRYPOINT` or `CMD`.

<Tip>
  Your built image freezes its base at build time. `:latest` is convenient while getting started; pin a dated Hermes base tag for reproducible production rebuilds. Find published tags [on GHCR](https://github.com/orgs/agent37-platform/packages/container/package/hermes-base), or read the current tag from `agent37-hermes`'s `image_ref` on `GET /v1/templates`.
</Tip>

## 2. Verify it locally (optional)

You don't need Docker to publish — the [cloud build](#build-it-in-the-cloud) does the real build. If you have Docker, a local build is still the fastest way to test before publishing. Plain `docker build` stores the amd64 result in your local Docker, including on an Apple Silicon Mac:

```bash theme={null}
docker build --platform linux/amd64 -t my-agent:v1 .
docker image inspect my-agent:v1 --format '{{.Os}}/{{.Architecture}}'
# linux/amd64
```

Run the image locally if it is practical: registration validates the image, but only a running container proves that its entrypoint and service work. If you take the [public registry path](#import-a-public-registry-image), this local `linux/amd64` build is also the artifact you push.

## 3. Register the image

You have two ways to get the image to Agent37. Either way the resulting image is capped at **8 GB decimal** (`8,000,000,000` bytes), and either way the platform copies it once into private storage at registration.

<Note>
  Registration is synchronous and can take a few minutes for a large image. The returned `image_digest` identifies the immutable private copy every instance runs. Re-pushing a mutable source tag does not change that copy.
</Note>

### Build it in the cloud

Use this path when the image isn't in a public registry — or doesn't exist yet. You upload a small **build context** (the `Dockerfile` and the files it `COPY`s), and Agent37 builds the image on its own infrastructure:

```bash theme={null}
export AGENT37_API_KEY=sk_live_...
npx agent37 templates build . --name my-custom-agent
```

What it does:

* Packs the directory (default `.`; it must have `Dockerfile` at its root) into a gzipped context, excluding `.git` and your `.dockerignore` patterns (plain patterns only; `!` negations are ignored). The context is capped at **100 MB** — it holds the Dockerfile's inputs, not the image, so it stays small.
* **Everything else in the folder ships with the context** — a stray `.env` or key file included, and a `COPY . .` bakes it into the image. Check the folder, or add a `.dockerignore`, before you build.
* Builds the image on Agent37's infrastructure and **streams the live build log** to your terminal. On failure the command exits non-zero with the failing step visible.
* Publishes the result as the workspace template. `--name` defaults to the folder name; `--default-port <port>` sets the template's default port. Re-building an existing name publishes a new template revision; **existing instances never change**.
* Ctrl-C does not cancel the build; it continues server-side and still publishes on success.

Builds are free, run one at a time per workspace, and time out after 45 minutes. The built image is capped at 8 GB decimal like any template image, and practically tops out around 4 GB today; if you need more, [contact support](mailto:support@agent37.com). Everything the build fetches — the `FROM` base image, anything `RUN` downloads — must be publicly reachable; there is no build-secret or private-registry-credential support yet. A template published this way has an `image_digest` but no `image_ref`, because there is no public registry reference.

To script the same flow without the CLI, see the raw contract in
[Templates → build an image in the cloud](/agents-api/templates#build-an-image-in-the-cloud).

### Import a public registry image

Push the image to any public OCI registry, then register its fully qualified reference. For example:

```bash theme={null}
docker tag my-agent:v1 ghcr.io/you/my-agent:v1
docker push ghcr.io/you/my-agent:v1
```

On GHCR, make the package public after the first push (**Packages** → **Package settings** → **Change visibility** → **Public**). The [starter example](https://github.com/agent37-platform/starter-kit/tree/main/examples/custom-agent-image) includes a GitHub Actions workflow that builds `linux/amd64` and publishes immutable commit tags.

Register a specific tag, not `latest`:

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/templates \
  -H "Authorization: Bearer $AGENT37_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "my-custom-agent", "image_ref": "ghcr.io/you/my-agent:v1" }'
```

Agent37 needs anonymous access only while it copies the image. The template and its instances no longer depend on the source registry after registration. A future image update needs a new public tag.

<Tip>
  If your own image serves HTTP on port `8000`, declare it: pass `--default-port 8000` on the build, or include `"default_port": 8000` beside `name` and `image_ref`. The bare instance URL then routes to that port, and instance creation probes it at boot. Omit it for `hermes-base`, whose gateway uses the `3737` fallback, or for a long-running private sandbox with no HTTP service.
</Tip>

## 4. Create an instance

Register once, then create instances from the template name:

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/instances \
  -H "Authorization: Bearer $AGENT37_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template": "my-custom-agent" }'
```

The result is a standard Agent37 instance running your image: same [lifecycle](/agents-api/instances), [exec](/agents-api/exec), and routed URLs as any other. Confirm your CLI shipped, without needing a model:

```bash curl theme={null}
curl -X POST https://api.agent37.com/v1/instances/<id>/exec \
  -H "Authorization: Bearer $AGENT37_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "command": "your-cli --version" }'
```

<Note>
  If the instance comes up `failed` instead of `running`, read its [logs](/agents-api/logs). An entrypoint that exits, a missing binary, or a service that never listens on `default_port` are the usual causes. `exec` cannot help until the container is running.
</Note>

## 5. Bring your own model

`hermes-base` is clean: it boots with no LLM provider (standard Agent37 instances use Agent37's managed model; this base is for bringing your own). To run an instance on your own model, point Hermes at any OpenAI-compatible endpoint — your own proxy, or a provider directly — by writing `~/.hermes/config.yaml` on the instance:

```yaml theme={null}
model:
  provider: "custom:MyProvider"
  default: "moonshotai/kimi-k2.7-code"        # the model id your endpoint serves
custom_providers:
  - name: "MyProvider"
    base_url: "https://your-llm-proxy.example.com/v1"   # must end in /v1
    api_key: "your-proxy-token"
    api_mode: "chat_completions"
    model: "moonshotai/kimi-k2.7-code"
```

Your endpoint must serve the two OpenAI-compatible routes Hermes uses: `GET /v1/models` (to resolve the model id) and `POST /v1/chat/completions` (the turn). The example folder includes a \~40-line `llm-proxy/` that implements exactly these and forwards to OpenRouter with your key — a minimal pattern to deploy and adapt.

Write the config over [exec](/agents-api/exec) or the instance terminal; it lives on the persistent volume, so it survives restarts. Then [send a message](/agents-api/chat) and the agent runs on your model.

<Tip>
  Want chat to work out of the box on Agent37's managed model instead? Build `FROM ghcr.io/agent37-platform/hermes:<tag>` ([tags on GHCR](https://github.com/orgs/agent37-platform/packages/container/package/hermes)) — the full image wires the managed model — and pass a [budget](/agents-api/budgets) on create.
</Tip>

## Keep it current

Your image freezes its base; rebuilding is how it picks up platform updates. Each template revision is an immutable snapshot, so a new build takes effect only after the template changes: re-run the [cloud build](#build-it-in-the-cloud) under the same name, or publish a **new registry tag** and [PATCH `image_ref`](/agents-api/templates#update-a-template). Every successful build and every changed `image_ref` increments the template's automatic `revision`; a re-build counts even when its digest matches the previous image. PATCHing the same `image_ref` string deliberately reuses the existing snapshot and revision.

Existing instances keep their installed `template_revision`. [Update each instance](/agents-api/instances#update) to recreate it from the template's current revision. A skill already seeded into an instance's `~/.hermes/skills` is not overwritten on update — only fresh instances get a changed skill.

## Troubleshooting

| Error or symptom                  | What to check                                                                                                                                                                       |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `build_failed`                    | A Dockerfile step failed. Read the build log — the CLI streams it, and `GET /v1/template-builds/{id}/logs` keeps it readable afterward; the failing step is at the end.             |
| `build_conflict`                  | One build runs at a time per workspace. Wait for the running build to finish, then start again.                                                                                     |
| `build_timeout`                   | The build ran past the 45-minute limit. Trim the Dockerfile's slowest steps and retry.                                                                                              |
| `image_too_large`                 | The built or imported image is over 8 GB decimal. Remove build caches and unnecessary layers, or contact support.                                                                   |
| `invalid_request` for `image_ref` | Use a fully qualified tag and confirm the registry allows anonymous pulls. For an image that isn't public, run a [cloud build](#build-it-in-the-cloud) from its Dockerfile instead. |
| `image_ingest_failed`             | The private copy failed on Agent37's side. Retry once — re-run the build or the registration — then contact `support@agent37.com`.                                                  |
| Instance status is `failed`       | Read [instance logs](/agents-api/logs); confirm the main process stays alive and `default_port` listens when set.                                                                   |
