---
title: Runners
description: "The three runner strategies — docker, compose and custom — what each one generates, how profile fields map onto them, resource limits, networking, sidecar orchestration, and the teardown differences"
canonical: https://giglabo.com/heretic/docs/heretic-cli/configuration/runners
locale: en
---

# Runners

> Markdown twin of https://giglabo.com/heretic/docs/heretic-cli/configuration/runners
> Fetch this instead of the HTML page: same content, a fraction of the bytes.
> Site structure and the full page list for agents: https://giglabo.com/llms.txt

The three runner strategies — docker, compose and custom — what each one generates, how profile fields map onto them, resource limits, networking, sidecar orchestration, and the teardown differences

The `runner:` field in the profile decides how the container is started.

## Comparison

| | `docker` | `compose` | `custom` |
|---|---|---|---|
| **Use case** | one container | agent plus companion services | full control with your own compose file |
| **Mechanism** | Docker API (dockerode) | generated compose file + `docker compose` | your compose file + `docker compose` |
| **Config source** | profile fields | profile fields plus the `compose:` block | `.heretic/cli/compose.yaml` |
| **Generated file** | none | `.heretic/temp/<session>/compose.yaml` | none |
| **Command override** | yes | yes | ignored |
| **Detached (`-d`)** | yes | yes (`up -d`) | yes |
| **Attach** | the agent container | the `agent` service | the first service reported by compose |
| **Build sidecars** | yes (dockerode-native) | yes (`depends_on: service_healthy`) | **rejected** |
| **Requires** | Docker daemon + `docker` CLI (for attach) | Docker Compose v2 | Docker Compose v2 + your file |

## `runner: docker` — a single container

The default. All profile fields apply: `image`, `volumes`, `env`, `extra`, `dind`, `mcp`, `secrets`, `tool_backends`.

```yaml
image: giglabo/claude-heretic:latest
runner: docker
provider: anthropic
```

Start-up sequence: ping the daemon (`Docker is not available. Please ensure Docker is running.`), pull the image when it is missing locally (`Failed to pull image <image>: …`), start the build sidecars if configured, remove any container with the same name, create and start the container, fix ownership of the mounted agent home, then attach (interactive) or return the ID (detached).

Interactive mode shells out to `docker attach`, which is more reliable for TTYs than an API stream — so the **docker CLI must be installed**, not just the socket.

### Resource limits and other `extra:` fields

| Field | Type | Example | Notes |
|---|---|---|---|
| `memory` | string | `"4g"`, `"512m"` | units `b`, `k`, `m`, `g`; anything else fails with `Invalid memory format` |
| `cpus` | string | `"2.0"`, `"0.5"` | fractional values allowed |
| `shm_size` | string | `"2g"` | size of `/dev/shm` |
| `capabilities` | array | `["SYS_PTRACE"]` | added Linux capabilities |
| `privileged` | boolean | `true` | full host access — avoid |
| `network` | string | `"host"`, `"bridge"`, `"none"` | network mode |
| `ports` | array | `["8080:80", "3000:3000"]` | `host:container`; a bare `"8080"` maps the same port both sides |
| `user` | string | `"1000:1000"` | `uid:gid` |
| `run_as_root` | boolean | `true` | root with `HOME=/home/agent` |
| `hostname` | string | `"my-agent"` | container hostname |
| `labels` | object | `{team: platform}` | applied after heretic's labels, so it can override them |

```yaml
extra:
  memory: "8g"
  cpus: "4.0"
  shm_size: "2g"
  network: host
  ports: ["3000:3000", "5173:5173"]
  capabilities: [SYS_PTRACE]
  privileged: false
```

> **Warning: extra.network and sidecars**
>
> Build sidecars need the agent on their private network. If you pin `extra.network`, your value wins and a warning is logged that the builders may be unreachable.

## `runner: compose` — agent plus services

Use it when the agent needs a database, a cache, or a local MCP server. The CLI writes `.heretic/temp/<session>/compose.yaml` and runs:

```
docker compose -f <file> -p heretic-<agent>-<session> up [-d]
```

The project name is lowercased with unsupported characters replaced by `-`. Compose v2 is required: `docker compose v2 is not available. …`

The agent is always the service named **`agent`** — do not reuse that name in `compose.services`.

```yaml
image: giglabo/claude-heretic:latest
runner: compose
provider: anthropic

extra:
  ports: ["3000:3000"]

compose:
  services:
    db:
      image: postgres:16
      environment:
        POSTGRES_PASSWORD: devpass
      ports: ["5432:5432"]
    redis:
      image: redis:7-alpine
      ports: ["6379:6379"]
```

### Structure of the generated file

```yaml
version: "3.8"
services:
  agent:                    # always "agent"
    image: …
    environment: …          # secrets + env + token injection + transformations
    volumes: …              # profile volumes + MCP + session mounts
    labels:
      heretic.managed: "true"
      heretic.agent: <name>
      heretic.project: <dir>
      heretic.session: <session>
    stdin_open: true
    tty: true
    working_dir: …
    command: [...]          # profile command or the CLI override
    network_mode: …         # from extra.*
    ports: [...]
    cap_add: [...]
    privileged: …
    user: …
    hostname: …
    deploy:
      resources:
        limits:
          memory: …
          cpus: …
    shm_size: …
    depends_on:             # when build sidecars are configured
      builder-node:
        condition: service_healthy

  db: …                     # your compose.services
  builder-node: …           # heretic's builder services, merged LAST

networks: …                 # your compose.networks
volumes: …                  # your compose.volumes + sidecar cache volumes
```

Your services are merged first and heretic's builder services last, so heretic wins on a name collision. Named volumes referenced by services — yours and the sidecar caches — are declared at the top level.

### The `compose:` block

| Field | Type | Description |
|---|---|---|
| `compose.services` | object | additional services; any Compose v3.8 service configuration |
| `compose.networks` | object | top-level `networks:` definitions |
| `compose.volumes` | object | top-level named volume declarations |

The block is **deep merged** by a local override (objects recurse; arrays and primitives replace), and it is ignored — with a validation warning — when the runner is not `compose`.

### Networking between services

All services share the compose default network and resolve each other by service name — the agent reaches PostgreSQL at `db:5432` and Redis at `redis:6379` with no extra configuration.

```yaml
compose:
  networks:
    internal:
      driver: bridge
  services:
    db:
      image: postgres:16
      networks: [internal]
```

### A local MCP server as a service

```yaml
image: giglabo/claude-heretic:latest
runner: compose
provider: anthropic

mcp:
  - name: my-local-server
    command: npx
    args: ["-y", "mcp-remote", "http://mcp-server:8080/sse"]

compose:
  services:
    mcp-server:
      image: my-mcp-server:latest
      environment:
        API_KEY: "${MY_API_KEY}"
```

> **Warning: Compose teardown deletes named volumes**
>
> The compose runner's own `stop()` runs `docker compose down --volumes`, which removes named volumes — including builder caches. `heretic-cli stop` works through labels instead and keeps them. Use `heretic-cli stop` when you want the caches to survive.

> **Note: Build sidecars and SSH**
>
> To route `npm`, `pip`, `mvn`, `go` or `cargo` into a sibling container or onto a remote host, see [Sidecars](https://giglabo.com/heretic/docs/heretic-cli/configuration/sidecars) and [SSH Host Access](https://giglabo.com/heretic/docs/heretic-cli/configuration/ssh).

## `runner: custom` — your own compose file

Use it when you need the full Compose spec: build contexts, health checks, `depends_on` conditions, several named volumes, elaborate networking.

You write `.heretic/cli/compose.yaml` and heretic runs it **verbatim** with `docker compose -f <file> -p heretic-<agent> up` — note the project name has no session component. A missing file fails at construction time:

```
Custom compose file not found: <path>
The custom runner requires a .heretic/cli/compose.yaml file in your project directory.
```

```bash
heretic-cli local-init --compose      # starter template
$EDITOR .heretic/cli/compose.yaml
```

```yaml
# profile
image: giglabo/claude-heretic:latest   # required by validation, unused by this runner
runner: custom
provider: anthropic

secrets:
  ANTHROPIC_API_KEY: ~/.heretic/get-anthropic-key.sh

env:
  ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
  MY_DB_PASSWORD: "${MY_DB_PASSWORD}"
```

### What the CLI injects

Nothing is written into your file. Instead these are exported into the `docker compose` **process environment**, so `${VAR}` references inside the compose file resolve:

- resolved `secrets:`
- profile `env:` values
- `GH_TOKEN`, `GITHUB_TOKEN`
- `GH_COPILOT_TOKEN`, `GITHUB_COPILOT_TOKEN` (for `provider: copilot`)

### Differences from `runner: compose`

| Feature | `compose` | `custom` |
|---|---|---|
| MCP config | generated and mounted | you manage it |
| session directory (`~/.claude`, `~/.copilot`) | mounted automatically | you manage it |
| onboarding seed (`.claude.json`) | created | not created |
| `extra:` fields | applied to the agent service | ignored |
| `compose:` block | merged into the generated file | ignored |
| command override | applied | ignored |
| build sidecars | supported | **rejected**: `Build sidecars require the 'docker' or 'compose' runner …` |
| attach target | the `agent` service | the first service reported by compose |

### Full example

```yaml
version: "3.8"

services:
  agent:
    image: giglabo/claude-heretic:latest
    environment:
      - ANTHROPIC_API_KEY
      - GH_TOKEN
      - NODE_ENV=development
    volumes:
      - ${PWD}:/workspace
      - agent-home:/home/agent
    working_dir: /workspace
    stdin_open: true
    tty: true
    networks: [app]
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: devpass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports: ["5432:5432"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks: [app]

  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]
    networks: [app]

networks:
  app:
    driver: bridge

volumes:
  pgdata:
  agent-home:
```

This waits for PostgreSQL to be healthy, persists the database and the agent home across restarts, and keeps everything on a dedicated bridge network.

## Choosing a runner

| Scenario | Runner |
|---|---|
| a single agent, nothing else | `docker` |
| the agent needs a database or cache | `compose` |
| the agent needs a local MCP server as a service | `compose` |
| you need build toolchains the image lacks | `docker` or `compose` with [sidecars](https://giglabo.com/heretic/docs/heretic-cli/configuration/sidecars) |
| you need health checks, build contexts or `depends_on` | `custom` |
| you already have a compose setup to integrate with | `custom` |

## Known quirks

- `isRunning()` for the compose runner parses `docker compose ps --format json` expecting a JSON **array**; newer Compose versions emit newline-delimited JSON, and the check then reports "not running" even though the stack is up. `heretic-cli ps`, which reads labels, is unaffected.
- The generated compose file is deleted when the CLI process exits (including on `SIGINT`/`SIGTERM`); `heretic-cli stop` does not need it because it works from labels.

## Next Steps

- [Sidecars](https://giglabo.com/heretic/docs/heretic-cli/configuration/sidecars) — build toolchains in sibling containers
- [SSH Host Access](https://giglabo.com/heretic/docs/heretic-cli/configuration/ssh) — the remote-host backend
- [Agent Profiles](https://giglabo.com/heretic/docs/heretic-cli/configuration/agent-profiles) — the full schema
- [Sessions](https://giglabo.com/heretic/docs/heretic-cli/configuration/sessions) — parallel instances

## Related

- HTML version of this page: https://giglabo.com/heretic/docs/heretic-cli/configuration/runners
- Site map for agents: https://giglabo.com/llms.txt
