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:

Bash
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:

Bash
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:

AbilityGrants
serverless:readContainer metadata, deployment history, revisions
serverless:writeCreate, update, deploy
serverless:deleteDelete
serverless:diagnosticsLogs 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.

JSON
{
  "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"
  }
}
FieldMeaning
summarypending, in_progress, ready, degraded, failed, stopped, deleting, unknown
healthhealthy, degraded, unhealthy, unknown — describes what is currently serving
operation.statequeued, running, succeeded, failed, cancelled
operation.terminalThe stop condition for polling
observed_atWhen the platform last checked the cluster — not when the row was last written
staleThe 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.

Bash
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.

Bash
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.

CodeMeaningRetryable
serverless.image_pull_authThe registry rejected the pull credentialNo — fix the credential
serverless.image_not_foundThe image or tag does not existNo — fix the reference
serverless.image_arch_mismatchImage architecture is not amd64No — rebuild for amd64
serverless.invalid_image_refThe image reference is malformedNo
serverless.image_pull_unknownThe image could not be fetched, cause unclearYes
serverless.config_errorThe container configuration was rejectedNo
serverless.oom_killedThe container exceeded its memory limitNo — raise the profile
serverless.crash_loopThe container starts and exits repeatedlyNo — fix the application
serverless.progress_deadlineThe rollout did not complete in timeYes
serverless.revision_missingThe expected revision is absentYes
serverless.unknownUnclassifiedYes

Retrying a non-retryable failure will not fix it and only consumes your build and request quota.

Diagnostic endpoints

text
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 at Ready=False with 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.

Bash
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:

statusMeaning
TrueSatisfied
FalseA settled verdict — this will not change on its own
UnknownStill 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:

Bash
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:

Bash
# 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.