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

# Provisioning

> Devcontainer features and runtimes catalog, crew provision lifecycle, and the cache image registry.

# Provisioning

Endpoints that drive the [Devcontainers](/guides/devcontainers) pipeline: discover available features and runtimes, trigger or rebuild a crew's cached image, and inspect the local cache.

Implementation lives in `internal/api/crew_provisioning_jobs.go` and `internal/api/crew_provisioning_cache.go`; the underlying provisioner is in `internal/devcontainer/`.

<Note>
  All routes require authentication and are workspace-scoped where they touch crew rows.
</Note>

## Endpoints

| Method | Endpoint                                                   | Purpose                                           |
| ------ | ---------------------------------------------------------- | ------------------------------------------------- |
| GET    | [`/api/v1/features/catalog`](#feature-catalog)             | List installable devcontainer features            |
| GET    | [`/api/v1/runtimes/catalog`](#runtime-catalog)             | List mise-managed runtimes                        |
| GET    | [`/api/v1/crews/{crewId}/provision`](#provision-status)    | Get crew provision status                         |
| POST   | [`/api/v1/crews/{crewId}/provision`](#trigger-provision)   | Trigger a provision                               |
| POST   | [`/api/v1/crews/{crewId}/rebuild`](#rebuild)               | Clear the cache marker and rebuild                |
| POST   | [`/api/v1/crews/{crewId}/restart-agents`](#restart-agents) | Recreate the runtime container without rebuilding |
| GET    | [`/api/v1/cache/images`](#list-cached-images)              | List cached images                                |
| DELETE | [`/api/v1/cache/images/{tag}`](#delete-cached-image)       | Delete a cached image                             |

***

## Catalogs

Discover the features and runtimes a crew can pin in its `devcontainer_config`.

### Feature catalog

```
GET /api/v1/features/catalog?search=…
```

Lists devcontainer features Crewship can install. The dynamic fetcher pulls from upstream registries (`ghcr.io/devcontainers/features/*`, `ghcr.io/devcontainers-extra/features/*`, `ghcr.io/crewship-ai/features/*`); when that fails, the handler falls back to `internal/devcontainer.FallbackCatalog` so the UI is never empty.

`?search=` filters by name, description, or category (server-side substring).

**Response:** `200 OK`

```json theme={null}
{
  "features": [
    {
      "ref": "ghcr.io/devcontainers/features/python:1",
      "name": "Python",
      "description": "Installs the specified Python version, pip, and pipx. Supports virtual environments.",
      "category": "languages",
      "icon": "code",
      "size_hint": "~80 MB"
    }
  ]
}
```

| Field       | Description                                                     |
| ----------- | --------------------------------------------------------------- |
| `ref`       | OCI reference — what you put in `devcontainer_config.features`. |
| `category`  | One of `languages`, `tools`, `cloud`, `databases`.              |
| `icon`      | Lucide icon name for UI rendering.                              |
| `size_hint` | Approximate installed-size delta (`~80 MB`).                    |

Unknown publishers (anything outside the three allowlisted prefixes) are rejected at provisioning time even if they appear in a custom catalog — see `internal/devcontainer/features.go`.

### Runtime catalog

```
GET /api/v1/runtimes/catalog?search=…
```

Lists the mise-managed runtimes (Node, Python, Go, Terraform, kubectl, …) the workspace can pin in `devcontainer_config.mise`. Falls back to `FallbackRuntimeCatalog` when the dynamic fetcher fails.

**Not** a base-image catalog. The base-image list is **CLI-only** — `crewship features base-images` reads `baseImagesCatalog` directly from `cmd/crewship/cmd_features.go`; there is no HTTP endpoint for it today.

**Response:** `200 OK`

```json theme={null}
{
  "runtimes": [
    {
      "name": "Node.js",
      "tool": "node",
      "description": "JavaScript runtime.",
      "category": "languages",
      "icon": "hexagon",
      "versions": ["22", "20", "18"],
      "default_version": "22",
      "backends": ["asdf"]
    }
  ]
}
```

| Field             | Description                                                                             |
| ----------------- | --------------------------------------------------------------------------------------- |
| `tool`            | Mise tool ID — what you put in `mise.json`.                                             |
| `category`        | Same set as the feature catalog: `languages`, `tools`, `cloud`, `databases`.            |
| `versions`        | Available versions, ordered newest-first. May be empty when mise resolves dynamically.  |
| `default_version` | What Crewship picks when the crew config does not pin one.                              |
| `backends`        | Mise install backends (`asdf`, `cargo`, `npm`, `pipx`, `github`). Empty → mise default. |

***

## Crew provisioning lifecycle

Inspect, trigger, rebuild, and recycle a crew's cached image and runtime container.

### Provision status

```
GET /api/v1/crews/{crewId}/provision
```

**Response:** `200 OK`. Always-present fields come from the `crews` row; the `step` / `total` / `message` / `steps` / `log_tail` / `started_at` / `completed_at` / `error` fields appear **only** when an in-memory job is currently running or recently finished for this crew.

```json theme={null}
{
  "status": "running",
  "devcontainer_config": "{\"image\":\"…\",\"features\":{…}}",
  "cached_image": null,
  "config_hash": "a1b2c3d4e5f6…",
  "agents_pending_restart": 0,
  "step": 3,
  "total": 6,
  "message": "installing devcontainer features",
  "steps": ["validate", "pull-base", "install-features", "install-mise", "snapshot", "tag"],
  "log_tail": ["Feature python installed", "Feature node installed"],
  "started_at": "2026-04-30T11:01:18Z"
}
```

`status` is one of:

| Value       | Meaning                                                                    |
| ----------- | -------------------------------------------------------------------------- |
| `idle`      | No active job and no `cached_image` — crew has nothing built or in flight. |
| `pending`   | Job enqueued, not yet picked up.                                           |
| `running`   | Provisioner is mid-pipeline.                                               |
| `completed` | Cached image exists at `cached_image` (no active job).                     |
| `failed`    | Job ended in failure. `error` carries the reason.                          |

| Field                    | When present | Meaning                                                                                              |
| ------------------------ | ------------ | ---------------------------------------------------------------------------------------------------- |
| `devcontainer_config`    | always       | Raw JSON blob pulled from `crews.devcontainer_config` (or null).                                     |
| `cached_image`           | always       | Last successfully committed cache tag, or null.                                                      |
| `config_hash`            | always       | SHA-256 of (base, devcontainer, mise) used as the cache key.                                         |
| `agents_pending_restart` | always       | Count of crew agents whose live container is on a stale image.                                       |
| `step` / `total`         | active job   | Numeric progress (e.g. `3 / 6`). The UI's `CrewProvisioningCard` renders a checklist from this pair. |
| `message`                | active job   | Free-form short message for the current step.                                                        |
| `steps`                  | active job   | Ordered list of step labels for the whole pipeline.                                                  |
| `log_tail`               | active job   | Trailing log lines (bounded) for inline display.                                                     |
| `started_at`             | active job   | RFC3339 start time.                                                                                  |
| `completed_at`           | finished job | RFC3339 end time (only after `completed`/`failed`).                                                  |
| `error`                  | failed job   | Human-readable failure reason.                                                                       |

### Trigger provision

```
POST /api/v1/crews/{crewId}/provision
```

Requires the `create` role action (OWNER, ADMIN, MANAGER). Returns 202 immediately and runs the pipeline in a background goroutine; poll the status endpoint for progress.

**Response:** `202 Accepted`

```json theme={null}
{
  "status": "started",
  "message": "Provisioning started. Monitor with 'crewship crew provision status <slug>'."
}
```

**Errors** (RFC 7807 Problem Details on every 4xx/5xx — `type`, `title`, `status`, `detail`, `instance`):

| Status | Condition                                                                                                                                                                                                                                                                                             |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Invalid crew ID, **or** crew has no `devcontainer_config`.                                                                                                                                                                                                                                            |
| 403    | Caller lacks `create` permission.                                                                                                                                                                                                                                                                     |
| 404    | Crew not in workspace.                                                                                                                                                                                                                                                                                |
| 409    | A provisioning job is already in progress for this crew. The 409 body extends Problem Details with `job_status` (the existing job's state) so callers can decide whether to wait or fall through. **Not** idempotent — the second caller is told to back off, not silently joined to the running job. |
| 429    | Per-workspace rate limit exceeded.                                                                                                                                                                                                                                                                    |
| 500    | Unexpected server-side error (panic-recovered).                                                                                                                                                                                                                                                       |
| 503    | Provisioner not configured (no Docker client wired).                                                                                                                                                                                                                                                  |

### Rebuild

```
POST /api/v1/crews/{crewId}/rebuild
```

Clears the cached image marker on the crew row and triggers a fresh provision. Use when upstream features publish breaking updates under the same tag.

<Warning>
  Discards the existing cache marker before rebuilding — the next agent run waits on a full pipeline pass.
</Warning>

**Response:** `202 Accepted` — same shape as Trigger.

### Restart agents

```
POST /api/v1/crews/{crewId}/restart-agents
```

Force-removes the crew's runtime container so the next agent exec recreates it from the current `cached_image` — without rebuilding the image. Used when an env-var change should take effect without the cost of a new image. Idempotent: when no container is running it still returns `200 OK` with `{ "restarted": 0 }`.

Requires the `update` role action (OWNER, ADMIN, MANAGER).

**Response:** `200 OK` with `{ "restarted": <count> }` — the count is the number of non-deleted agents in the crew (they share one container, so all are recreated together).

| Status | Condition                                     |
| ------ | --------------------------------------------- |
| 400    | Empty crew ID.                                |
| 403    | Caller lacks `update` permission.             |
| 404    | Crew not in workspace.                        |
| 500    | DB error or Docker `ContainerRemove` failure. |
| 503    | Docker client not configured.                 |

***

## Cache image registry

Inspect and prune the locally built `crewship-cache:*` images. This registry is **per-host, not per-workspace** — see [Tenancy](#tenancy).

### List cached images

```
GET /api/v1/cache/images
```

**Response:** `200 OK`

```json theme={null}
{
  "images": [
    {
      "tag": "crewship-cache:a1b2c3d4e5f6",
      "size": 824191022,
      "created_at": 1714415721,
      "referenced_by": ["backend"]
    }
  ]
}
```

| Field           | Type      | Description                                                                                     |
| --------------- | --------- | ----------------------------------------------------------------------------------------------- |
| `tag`           | string    | The `crewship-cache:*` image tag.                                                               |
| `size`          | integer   | Image size in bytes.                                                                            |
| `created_at`    | integer   | Image creation time as **Unix seconds** (from the Docker image summary), not an RFC3339 string. |
| `referenced_by` | string\[] | Slugs of live crews referencing this tag (workspace-scoped).                                    |

Requires the `read` role action (any workspace member). Returns `503` (`{ "error": "cache management not available (Docker client not configured)" }`) when no Docker client is wired, and `500` on a Docker `ImageList` failure.

Images orphaned from any crew (i.e. `referenced_by` is empty) are eligible for the GC sweeper. See [`CREWSHIP_CACHE_GC_AUTODELETE`](/configuration/environment#devcontainer-cache-gc).

### Delete cached image

```
DELETE /api/v1/cache/images/{tag}
```

Removes a cached image. By default refuses to delete an image still referenced by a live crew (returns `409 Conflict`). Pass `?force=true` to delete anyway.

<Warning>
  `?force=true` removes an image even while live crews reference it — their next agent run rebuilds from scratch.
</Warning>

Requires the `delete` role action (**OWNER, ADMIN** only — a `MANAGER` is rejected with `403`).

**Response:** `200 OK`

```json theme={null}
{ "tag": "crewship-cache:a1b2c3d4e5f6", "status": "deleted" }
```

A `409 Conflict` body lists the referencing crew slugs under `referenced_by`. The reference check is **host-global** — it counts crews in *any* workspace, so an image another tenant still uses cannot be force-free-deleted by accident:

```json theme={null}
{
  "error": "image is referenced by live crews; pass ?force=true to delete anyway",
  "referenced_by": ["backend"]
}
```

**Errors:**

| Status | Condition                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------------------- |
| 400    | Empty `tag`, or `tag` outside the `crewship-cache:` namespace (`{ "error": "only crewship-cache:* tags may be deleted" }`). |
| 403    | Caller is below the `ADMIN` role.                                                                                           |
| 409    | Image still referenced by a live crew and `?force=true` not set.                                                            |
| 500    | Docker `ImageRemove` failed.                                                                                                |
| 503    | Docker client not configured (`{ "error": "cache management not available (Docker client not configured)" }`).              |

***

## Tenancy

* `crewId` on the provision-status / trigger / rebuild / restart-agents routes is validated against the session workspace via the `crews.workspace_id` SELECT; a crew in another workspace returns `404`.
* The cache image registry is **per-host, not per-workspace** — operators sharing a Docker daemon will see each other's tags, and the cache routes do **not** workspace-scope the `{tag}` path param. `GET /cache/images` scopes only the `referenced_by` annotation to the caller's workspace; `DELETE /cache/images/{tag}` deliberately checks references **across all workspaces** so a tag another tenant depends on cannot be deleted without `?force=true`. There is no cross-tenant `404` on a `{tag}` — the tag namespace is shared by design.

## Related

* [Devcontainers guide](/guides/devcontainers) — full provisioning pipeline.
* [`crewship features`](/cli/features), [`crewship runtimes`](/cli/runtimes) — CLI wrappers.
* [Configuration — Devcontainer Cache GC](/configuration/environment#devcontainer-cache-gc) — `CREWSHIP_CACHE_GC_AUTODELETE`.
