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