# Rapids Runs

> **Preview:** Rapids runs are an early-access feature, available to select accounts while the API is reviewed. The shape described here may still change before general availability.

A run executes your Rapids container's image **once**, on demand — the same image and environment your container serves traffic from, but started as a single job instead of a scaled service. This is the right tool for one-off work that shouldn't run as part of normal request traffic: database migrations, one-time backfills, cache warm-ups, or a maintenance script bundled into your image.

Runs are separate from your container's normal scaling. Starting a run does not scale your container up or down, and a run in progress does not affect the replicas serving live traffic.

## Why not just scale the container?

Scaling a container's replicas to run a one-off script — for example to apply a database migration before a deploy — runs into a mismatch: scaling creates a Knative **revision**, and a revision's startup work can run more than once (Knative may start additional replicas of the same revision under load, or retry a replica that failed to become ready). A migration that isn't safe to run twice concurrently should not be triggered this way.

A run avoids this: it executes your image exactly once, as a single Kubernetes Job, with no autoscaling and no retries.

## Starting a run

```bash
curl -X POST https://api.danubedata.ro/api/v1/serverless/{container_id}/runs \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "command": ["php", "artisan", "migrate", "--force"],
    "timeout_seconds": 900
  }'
```

All fields are optional:

| Field | Type | Description |
|-------|------|--------------|
| `command` | `string[]` | Overrides the image's entrypoint for this run. 1–32 items, each up to 4096 characters. Omit it to run the image's own default startup command. |
| `image_tag` | `string` | Run a different tag of your container's **own image repository** for this run only — e.g. to run a migration from a specific build. The repository itself can never be changed by a run. |
| `env` | `object` | Extra environment variables for this run only, layered on top of (and overriding) your container's normal environment. Up to 50 keys, each `UPPER_SNAKE_CASE`-style (`[A-Za-z_][A-Za-z0-9_]*`). **Values are never stored or returned** — only the key names appear in the run object, so you can see what was overridden without the values being retrievable afterward. |
| `timeout_seconds` | `integer` | How long the run may execute before it is stopped and marked timed out. 60–3600 (1 hour max). Defaults to 900 (15 minutes). |

A successful request returns `202` with the new run:

```json
{
  "success": true,
  "data": {
    "id": "0192f3b0-...",
    "container_id": "0192f1a0-...",
    "status": "queued",
    "terminal": false,
    "command": ["php", "artisan", "migrate", "--force"],
    "image": "cr.danubedata.ro/acme/api:latest",
    "env_keys": [],
    "timeout_seconds": 900,
    "exit_code": null,
    "message": null,
    "created_at": "2026-09-16T18:00:00+00:00",
    "started_at": null,
    "finished_at": null,
    "duration_seconds": null
  },
  "error": null,
  "meta": {}
}
```

### Only one run at a time per container

A container can have at most one run **queued** or **running** at once. Starting a second run while one is already active returns `409`:

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "serverless.run_in_progress",
    "message": "A run is already in progress for this container.",
    "retryable": true
  },
  "meta": { "active_run_id": "0192f3b0-..." }
}
```

Wait for the active run to finish (or [cancel it](#cancelling-a-run)) before starting another.

## Run status

| Status | Terminal | Meaning |
|--------|----------|---------|
| `queued` | No | Accepted; the Kubernetes Job is being created. |
| `running` | No | The image is executing. |
| `succeeded` | Yes | The image exited with status `0`. |
| `failed` | Yes | The image exited with a non-zero status, or the run could not be started. See `message`. |
| `cancelled` | Yes | Stopped by a `cancel` request. |
| `timed_out` | Yes | Still running when `timeout_seconds` elapsed. |

Poll `GET /api/v1/serverless/{container_id}/runs/{run_id}` until `terminal` is `true`.

## Listing runs

```bash
curl https://api.danubedata.ro/api/v1/serverless/{container_id}/runs \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

Returns runs newest first, 20 per page, with pagination in `meta`:

```json
{
  "success": true,
  "data": [ { "id": "...", "status": "succeeded", "...": "..." } ],
  "error": null,
  "meta": { "current_page": 1, "per_page": 20, "total": 3 }
}
```

## Reading logs

```bash
curl https://api.danubedata.ro/api/v1/serverless/{container_id}/runs/{run_id}/logs \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

```json
{
  "success": true,
  "data": {
    "run_id": "0192f3b0-...",
    "source": "live",
    "logs": "Migrating: 2026_09_16_190000_create_..."
  },
  "error": null,
  "meta": {}
}
```

`source` is `live` while the run is still active (read directly from its pod), `stored` once the run has finished (a tail is captured at completion), or `none` if no logs are available yet.

## Cancelling a run

```bash
curl -X POST https://api.danubedata.ro/api/v1/serverless/{container_id}/runs/{run_id}/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

Cancelling a `queued` or `running` run stops it immediately and marks it `cancelled`. Cancelling a run that has already finished returns `409` with `error.code: "serverless.run_not_active"`.

## Limits

- **Timeout:** 60–3600 seconds (1 hour), 900 by default.
- **Command:** up to 32 arguments, each up to 4096 characters.
- **Environment overrides:** up to 50 keys per run; values are not persisted or returned.
- **Concurrency:** one queued or running run per container.
- **Resources:** a run uses the same CPU and memory profile as your container's own replicas.

## API abilities

| Endpoint | Ability |
|----------|---------|
| `GET /runs`, `GET /runs/{id}` | `serverless:read` |
| `POST /runs`, `POST /runs/{id}/cancel` | `serverless:write` |
| `GET /runs/{id}/logs` | `serverless:diagnostics` |

## Next steps

- [Invoking Containers](https://docs.danubedata.ro/serverless-invoking) — how your container serves normal traffic
- [Automation & Diagnostics](https://docs.danubedata.ro/rapids-automation) — inspecting a container's live state

---

**Questions?** Contact support at support@danubedata.ro
