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
In the console, open your container and go to the Runs tab. "Run" asks for the command, any environment variables for that run alone, an image tag and a timeout; the list below shows each run's status, exit code, duration and logs, and refreshes itself while a run is in progress.
The console splits the command into arguments the way a shell does, so quote an argument that contains spaces: node -e "console.log('hi')" runs node with two arguments, -e and the script. The command itself does not run in a shell, though: $VARIABLES, |, && and > reach your program as literal text. For those, start a shell yourself, as in sh -c 'npm run build && npm run migrate'. The run list shows each command quoted the same way, so you can see where one argument ends and the next begins.
From the API:
curl -X POST https://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 — for a web app image, that command is usually the server itself, which then keeps running (and billing) until timeout_seconds is reached, and blocks any other run from starting until it ends. The console requires an explicit confirmation before starting a run or creating a schedule with no command, for this reason. |
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:
{
"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:
{
"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) before starting another.
Calling your container from a run
A run can call its own container, for example to trigger a cron endpoint, at either address:
- The container's public address,
https://<name>-<namespace>.danubedata.run, or one of its custom domains. - The Internal cluster URL on the container's Overview tab,
http://<name>.<namespace>.svc.cluster.local. Use it without a port: it is served on port 80 and forwarded to your container's own port, so adding:8080won't work.
Run status
| Status | Terminal | Meaning |
|---|---|---|
queued | No | Accepted. The run is being created, or it is waiting for room to start. |
running | No | The run has been handed to the cluster. It may still be waiting for room, pulling its image, or 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.
A run that cannot start right away, because there is no room for it at that moment, waits. It is shown as running at first, goes back to queued within about a minute, and stays there until there is room. Its timeout keeps counting while it waits. A run that is still waiting when the timeout elapses ends as failed, with a message that says it never started, and not as timed_out. A waiting run still counts as the container's one active run: cancel it if you want to start another.
Listing runs
curl https://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:
{
"success": true,
"data": [ { "id": "...", "status": "succeeded", "...": "..." } ],
"error": null,
"meta": { "current_page": 1, "per_page": 20, "total": 3 }
}
Reading logs
curl https://danubedata.ro/api/v1/serverless/{container_id}/runs/{run_id}/logs \
-H "Authorization: Bearer YOUR_API_TOKEN"
{
"success": true,
"data": {
"run_id": "0192f3b0-...",
"source": "live",
"logs": "Migrating: 2026_09_16_190000_create_..."
},
"error": null,
"meta": {}
}
source tells you where the output came from:
source | Meaning |
|---|---|
live | read straight from the run's pod, while it is still running |
archive | read from the log store, which keeps a finished run's full output for 10 days |
stored | the tail captured when the run finished — the fallback when the log store has nothing |
none | no logs are available, including after stored output expires |
A finished run is answered from the archive when possible. That matters an hour after a run ends: its pod is removed at that point, and the captured tail is only the last 1000 lines, so the archive is where a long migration's earlier output survives.
The captured tail expires ten days after the run finishes. After that point the logs endpoint returns no output, even if a stored copy is awaiting the hourly cleanup that removes its text. Run history, status, timing and diagnostic error summaries remain. Active run output is not purged. This policy is not customer-configurable and does not guarantee deletion from infrastructure backups or node-local logs. See Logs.
Runs do not automatically scrub sensitive data from command output. Use synthetic data when testing and keep secrets, patient details and sensitive query values out of both normal output and error messages.
Cancelling a run
curl -X POST https://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".
Scheduling a run
A schedule runs the same thing on a repeating basis — a nightly migration, an hourly cleanup — without you calling the API each time.
In the console, schedules live under the same Runs tab, below your runs: "New schedule" asks for a name, when it repeats, and the command, which is split into arguments as described under Starting a run. From the CLI, danube rapids schedules create. Over the API:
curl -X POST https://danubedata.ro/api/v1/serverless/$CONTAINER/schedules \
-H "Authorization: Bearer $DANUBEDATA_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "nightly-migrate",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Bucharest",
"command": ["php", "artisan", "migrate", "--force"]
}'
| Field | Notes |
|---|---|
name | lowercase letters, numbers and hyphens; unique per container |
cron_expression | standard five-field cron, e.g. 0 3 * * * for 3am daily |
timezone | any IANA zone, UTC by default |
command | as for a run; omit to use the image's entrypoint |
image_tag | pin a tag, or omit to follow whatever your container is on |
timeout_seconds | 60–3600, 900 by default |
enabled | true by default; set false to pause without deleting |
The expression is read in your timezone. 0 3 * * * with Europe/Bucharest means 3am in Bucharest all year — it does not drift by an hour when the clocks change.
A schedule takes no environment overrides. A run can carry them for the length of that one run, but a schedule is stored, and we do not keep your secrets at rest. A scheduled run uses your container's own environment.
If the previous run is still going, the occurrence is skipped — the one-run-at-a-time rule applies to scheduled runs too. The skip is recorded rather than swallowed, so you can tell a deliberate skip from a broken schedule:
{
"next_run_at": "2026-09-19T00:00:00+00:00",
"last_run_at": "2026-09-17T00:00:00+00:00",
"last_skipped_at": "2026-09-18T00:00:00+00:00",
"last_skip_reason": "The previous run was still going when this one came due."
}
A skipped occurrence is not retried — the schedule moves on to its next one.
List, change and remove them with GET, PATCH and DELETE on /schedules and /schedules/{id}. Changing when a schedule runs re-arms it immediately. Deleting one leaves the runs it already produced in your history.
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.
What a run costs
A run is billed exactly like one of your container's own replicas: the same vCPU-second and GiB-second rates, against the same monthly free tier, using the CPU and memory your container's profile allocates. A one-minute run on a Small container is one minute of a Small container — no separate run charge, no per-run minimum.
Billing starts when the run is given room on a server and stops when it ends, so a run that is rejected before that — by the one-at-a-time rule, or by your namespace quota — costs nothing. A run that has to wait for room is not billed for the wait. A wait of under a minute can pass unnoticed, and is then billed. From then on the CPU and memory are held for the run: one that fails, times out, or that you cancel is billed up to that moment, and so is one whose image cannot be pulled. Cancel a run that is not starting rather than leaving it to its timeout.
Runs appear in your container's usage alongside its serving traffic rather than as a separate line.
Webhooks
If your team has webhooks enabled, a finished run delivers one of two events:
| Event | When |
|---|---|
serverless.run_succeeded | the run exited 0 |
serverless.run_failed | the run exited non-zero, or hit its timeout |
Cancelling a run sends nothing — you already know.
{
"event": "serverless.run_failed",
"timestamp": "2026-09-18T09:14:22+00:00",
"team_id": 42,
"data": {
"run": {
"id": "01a0b33a-353c-7096-90ae-345c9f0d00e7",
"status": "timed_out",
"exit_code": null,
"message": "The run did not finish within its 900s timeout.",
"command": ["php", "artisan", "migrate", "--force"],
"image": "cr.danubedata.ro/acme/api:2026-09-18",
"env_keys": ["APP_ENV"],
"timeout_seconds": 900,
"started_at": "2026-09-18T08:59:22+00:00",
"finished_at": "2026-09-18T09:14:22+00:00",
"duration_seconds": 900
},
"container": { "id": "…", "name": "acme-api", "slug": "acme-api" }
}
}
A timed-out run arrives as serverless.run_failed with status: "timed_out" — the event name tells you it did not succeed, and status tells you why.
The payload carries no log output. Fetch logs from GET /runs/{id}/logs when you need them, while the run's pod is still around.
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 |
GET /schedules, GET /schedules/{id} | serverless:read |
POST /schedules, PATCH /schedules/{id}, DELETE /schedules/{id} | serverless:write |
Next steps
- Invoking Containers — how your container serves normal traffic
- Automation & Diagnostics — inspecting a container's live state
Questions? Contact support at support@danubedata.ro