Rapids automation and diagnostics
This page covers scripts, CI pipelines and AI agents that deploy and debug Rapids containers without a human watching. Field names and value domains here change additively, never in place.
Read Platform automation first. The status model, polling rules, envelope, capabilities and diagnose contract described there are identical for every DanubeData product — cache, databases, queues, VPS, object storage, static sites, managed apps and more. This page restates them in Rapids terms and then covers what only Rapids has: image preflight, endpoint probing, revisions and deployment operations.
Every failure code is catalogued at Failure codes.
Authentication and project scope
All requests use a bearer token against https://danubedata.ro:
curl -H "Authorization: Bearer $DANUBE_TOKEN" \
-H "X-Team-Id: 42" \
https://danubedata.ro/api/v1/serverless
X-Team-Id selects the project. If you omit it, the account default is used — rarely what automation wants. With the CLI:
danube --project 42 rapids get my-api --json
danube project select --project 42 # persists the default, no prompt
--team is accepted as an alias. Supplying both with different values is an error rather than a silent preference, so automation cannot end up pointed at the wrong project without noticing.
Token abilities:
| Ability | Grants |
|---|---|
serverless:read | Container metadata, deployment history, revisions |
serverless:write | Create, update, deploy |
serverless:delete | Delete |
serverless:diagnostics | Logs and platform events |
Logs are gated separately from serverless:read on purpose: a token that may list your containers should not automatically be able to read what those containers print.
The status model
Every container response carries status_details. Read this rather than the legacy status string — it is the same object the dashboard and the websocket broadcast use, so they cannot disagree with you.
{
"summary": "failed",
"health": "unhealthy",
"observed_at": "2026-08-03T18:59:00+00:00",
"stale": false,
"operation": { "state": "failed", "terminal": true },
"error": {
"code": "serverless.image_pull_auth",
"source": "reconciler",
"resource": { "kind": "Revision", "name": "my-api-00007" },
"reason": "ContainerMissing",
"message": "The registry rejected the configured pull credential.",
"retryable": false,
"observed_at": "2026-08-03T18:59:00+00:00"
}
}
| Field | Meaning |
|---|---|
summary | pending, in_progress, ready, degraded, failed, stopped, deleting, unknown |
health | healthy, degraded, unhealthy, unknown — describes what is currently serving |
operation.state | queued, running, succeeded, failed, cancelled |
operation.terminal | The stop condition for polling |
observed_at | When the platform last checked the cluster — not when the row was last written |
stale | The platform could not reach a live source; the summary is the last known one |
Polling correctly
Poll until operation.terminal is true. Do not infer terminality from the status string, and do not treat health: "unknown" as a failure — during a rollout the platform genuinely does not yet know whether the new revision is healthy, and saying so is more honest than guessing.
until [ "$(danube rapids get my-api --json | jq -r '.status_details.operation.terminal')" = "true" ]; do
sleep 5
done
summary: "degraded" is terminal but not an outage: a new revision failed while an older one keeps serving traffic. Your site is up. Redeploying the same configuration will fail the same way.
Waiting for your own change
Every change that needs a rollout — a create, an update, a redeploy — is given a number. The response to that request carries it as spec_generation, and the container reports observed_generation: the number the latest rollout started from. Your change is being rolled out once observed_generation has reached your number, and it has finished once operation.terminal is true as well.
GEN=$(danube rapids update my-api --tag "$SHA" --json | jq -r '.spec_generation')
until danube rapids get my-api --json \
| jq -e --argjson gen "$GEN" '.observed_generation >= $gen and .status_details.operation.terminal' >/dev/null; do
sleep 5
done
danube rapids update --wait (CLI 1.3.0 and later) does exactly this.
While a change is waiting for its rollout, operation.state is queued and operation.terminal is false, even if the container itself is running — the settled status describes the previous rollout, not yours.
Rollouts of one container run one at a time, in order. A change sent while a rollout is still in progress is queued behind it and rolled out next; several changes sent before a queued rollout starts are rolled out together. Nothing is dropped.
During a rollout the previous revision keeps serving until the new one is ready, and for a few minutes after that — max-scale limits each revision, not the container. If your container does one-off work when it starts (database migrations, for example), expect it to run once per revision and guard it with a lock.
Failure codes
status_details.error.code is the automation key; message is for humans and may be reworded. retryable says whether trying again can possibly help.
| Code | Meaning | Retryable |
|---|---|---|
serverless.image_pull_auth | The registry rejected the pull credential | No — fix the credential |
serverless.image_not_found | The image or tag does not exist | No — fix the reference |
serverless.image_arch_mismatch | Image architecture is not amd64 | No — rebuild for amd64 |
serverless.invalid_image_ref | The image reference is malformed | No |
serverless.image_pull_unknown | The image could not be fetched, cause unclear | Yes |
serverless.config_error | The container configuration was rejected | No |
serverless.oom_killed | The container exceeded its memory limit | No — raise the profile |
serverless.crash_loop | The container starts and exits repeatedly | No — fix the application |
serverless.progress_deadline | The rollout did not complete in time | Yes |
serverless.revision_missing | The expected revision is absent | Yes |
serverless.unknown | Unclassified | Yes |
Retrying a non-retryable failure will not fix it and only consumes your build and request quota.
Diagnostic endpoints
GET /api/v1/serverless/{id}/diagnose
GET /api/v1/serverless/{id}/logs
GET /api/v1/serverless/{id}/revisions
GET /api/v1/serverless/{id}/events
These return a {success, data, error, meta} envelope.
Diagnose
/diagnose does the correlation for you and returns ranked findings. It reads the status, the latest Revision and the Route together — which is what the three manual steps below amount to — so start here and drop to the raw endpoints only when you want to see what it was looking at.
Two findings are worth knowing by name, because nothing else reports them:
serverless.no_pod_scheduled— the revision settled atReady=Falsewith zero replicas, so no pod ever ran. An empty log is the expected consequence, not a second problem to chase.serverless.ingress_not_ready— the revision is healthy and the public URL still does not serve. Every other signal says the deploy succeeded.
Revision checks are skipped while an operation is in flight and for a stopped container: a healthy deploy is briefly Ready=False with zero replicas, and a stopped container is meant to have no pods. Full list at Failure codes.
Logs
Parameters: since, until, cursor, limit, level, container.
container is one of user-container (your process), queue-proxy (the request sidecar) or all. level is one of debug, info, warn, error.
danube rapids logs my-api --since 1h --level error --json
data.available is not the same as an empty data.entries. An empty array with available: true means your container printed nothing. available: false with HTTP 503 means the log store did not answer — that says nothing about your container, and is worth retrying.
Always pass since. Without it the query covers only the last 30 minutes, so a container that failed an hour ago legitimately returns no entries.
Page with meta.next_cursor. When meta.truncated is false and next_cursor is null, you have reached the end of the stream within the requested window — widen since to look further back. Logs are retained for 10 days; a since older than that is rejected rather than silently clamped.
level matches the text of each line rather than a structured log level, so a line that merely mentions "error" will match level=error. Treat it as a filter, not a guarantee.
Revisions
Returns every revision, not only those receiving traffic — the revision that failed is precisely the one with no traffic.
Conditions are tri-state and the middle state carries meaning:
status | Meaning |
|---|---|
True | Satisfied |
False | A settled verdict — this will not change on its own |
Unknown | Still in progress |
Treating Unknown as a failure is the most common mistake here: it makes an in-flight deploy look like an outage.
The response also includes Service and Route readiness. Route conditions include IngressReady, which is what separates "the deployment succeeded" from "the URL still returns 404".
Events
Platform events for your container's service, revisions and pods. Requires serverless:diagnostics.
Events are ephemeral — the platform garbage-collects them, so their absence is not evidence that nothing happened. Revision conditions are the durable signal; prefer them when the two disagree.
Debugging a failed deploy
Ask the platform first — it correlates the three sources below in one call:
curl -sH "Authorization: Bearer $TOKEN" \
https://danubedata.ro/api/v1/serverless/$ID/diagnose | jq '.data.findings'
To see the underlying signals yourself, or when a finding needs corroborating:
# 1. Is it terminal, and why?
danube rapids get my-api --json | jq '.status_details'
# 2. What did the platform observe?
danube rapids revisions my-api --json | jq '.data.revisions[0].conditions'
# 3. Did the container produce output?
danube rapids logs my-api --since 30m --json
If step 2 shows Ready=False with reason ContainerMissing, the image was never fetched, so no pod was ever created and step 3 will legitimately return nothing. That is not a logging problem — fix the image reference or the registry credential and redeploy.
Endpoints and DNS
Rapids containers are served from *.danubedata.run. Managed database, cache and queue endpoints use *.danubedata.ro. These are different domains; do not assume a Rapids URL follows managed-service DNS rules.
For custom domains, see Rapids custom domains.