Skip to content

Node pools and nodes

A node pool is a group of workers that share one machine tier. You add, scale, autoscale, and remove pools on a Provisioned cluster. You can also delete a single worker; Door replaces it so the pool's desired count stays the same.

Who can do this: member lists pools and nodes. super_admin adds, patches, removes pools, and deletes a node.

All mutations below return 409 unless phase=Provisioned (including ControlPlaneReady and NodesReady). A second pool operation on the same pool while one is still running is also 409. Different pools may be changed in parallel.

Console: cluster → Nodes (/dks/clusters/<id>/nodes). Maya: “Scale the workers pool on payments-prod to 4.” Approve.

List pools

GET /v1/public/clusters/{id}/node-pools → 200

curl -sS \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools" \
  -H "Authorization: Bearer $DOOR_TOKEN"
{
  "schema_version": "nodepool.public.v1",
  "cluster_id": "3f9c1a2e-7b4d-4c58-9e21-5d6f8a0b1c2d",
  "items": [
    {
      "id": "a1b2c3d4-0000-4000-8000-000000000001",
      "name": "workers",
      "size": "dks.c5.xlarge",
      "count": 2,
      "auto_scale": false,
      "min_nodes": null,
      "max_nodes": null,
      "labels": {},
      "tags": {},
      "root_volume_size_gib": 80,
      "lifecycle": "active"
    }
  ],
  "total": 1
}

GET /v1/public/clusters/{id} also embeds node_pools with the same item shape. Pool list does not embed nodes — use the node routes below.

lifecycle is active or deleting (after a pool delete is accepted, until it is gone).

Add a pool

POST /v1/public/clusters/{id}/node-pools → 202 and the full cluster body (updated node_pools).

PublicNodePoolCreateRequest

Field Type Default Constraints
name DNS label — required Unique among alive pools. Duplicate → 409.
size string — required Catalog machine tier id. Unknown → 400.
count int 1 0–200. count=0 only with auto_scale=true.
auto_scale bool false
min_nodes int or null null → 1 when autoscaling ≥ 0
max_nodes int or null null Required when auto_scale=true; ≥ min_nodes; count in range.
labels object {}
tags object {}

Same autoscaling rules as create. Invalid combination → 422. 402 if prepaid credit cannot cover the extra capacity.

curl -sS -X POST \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools" \
  -H "Authorization: Bearer $DOOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "batch",
    "size": "dks.m5.xlarge",
    "count": 1
  }'

Follow with GET …/state (operations[] shows the pool activity while phase stays Provisioned).

Maya: “Add a node pool named batch on payments-prod with one dks.m5.xlarge.” Approve the confirmation card.

Scale and enable autoscaling

PATCH /v1/public/clusters/{id}/node-pools/{name} → 202 and the cluster body.

PublicNodePoolPatchRequest

Only mutable fields. size is not accepted (immutable). To change tier: add a new pool, wait until it is up, then delete the old one.

Field Type Notes
count int or omit 0–200
auto_scale bool or omit
min_nodes int or omit
max_nodes int or omit
labels object or omit Replaces the stored map when sent
tags object or omit Replaces the stored map when sent

Omitted keys keep the current row. Sending no fields → 422: at least one of: count, auto_scale, min_nodes, max_nodes, labels, tags.

The handler merges your patch with the current pool, then validates:

Rule Error
auto_scale=true and max_nodes missing (after merge) 422 max_nodes is required when auto_scale=true
min_nodes > max_nodes 422
count outside [min_nodes, max_nodes] when autoscaling 422
auto_scale=false and count=0 422 count=0 requires auto_scale=true (or use DELETE to remove the pool)

If you turn autoscaling on without sending bounds, Door fills min_nodes (default 1) and requires max_nodes from you or the existing row.

Optional Idempotency-Key behaves as on create (same key + same patch body replays 202).

# Fixed size: 2 → 4
curl -sS -X PATCH \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools/workers" \
  -H "Authorization: Bearer $DOOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "count": 4 }'

# Turn on autoscaling
curl -sS -X PATCH \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools/workers" \
  -H "Authorization: Bearer $DOOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "auto_scale": true, "min_nodes": 2, "max_nodes": 6, "count": 2 }'

409: cluster not Provisioned, or a pool operation still in flight. 404: unknown pool name (or already deleting). 402: credit gate.

Remove a pool

DELETE /v1/public/clusters/{id}/node-pools/{name} → 202.

The cluster must keep at least one alive pool. Deleting the last pool returns 409. Add a replacement pool first, then delete the old one.

curl -sS -X DELETE \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools/batch" \
  -H "Authorization: Bearer $DOOR_TOKEN"

The pool's lifecycle becomes deleting until workers are gone.

List nodes

Two reads, same stale-while-revalidate contract. Always 200 with the best-known snapshot (never 202). 409 cluster not ready yet until Provisioned. 404 if the pool name does not exist. 503 if inventory is temporarily unavailable.

Route Scope
GET /v1/public/clusters/{id}/nodes Every worker on the cluster
GET /v1/public/clusters/{id}/node-pools/{pool}/nodes One pool

Freshness fields on the envelope:

Field Meaning
cold true on the first read (no snapshot yet). items may be empty. A refresh is queued.
stale true when the snapshot is older than the cache TTL. A refresh is queued.
next_refresh_at When the background refresh is expected (if one was enqueued). Poll again after that.
curl -sS \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/nodes" \
  -H "Authorization: Bearer $DOOR_TOKEN"

curl -sS \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/node-pools/workers/nodes" \
  -H "Authorization: Bearer $DOOR_TOKEN"
{
  "schema_version": "node.public.v1",
  "cluster_id": "3f9c1a2e-7b4d-4c58-9e21-5d6f8a0b1c2d",
  "items": [
    {
      "name": "payments-prod-workers-abc12",
      "pool": "workers",
      "status": "Running",
      "kubernetes_version": "v1.32.4",
      "ready": true,
      "created_at": "2026-10-01T12:04:00Z",
      "deleted_at": null
    }
  ],
  "total": 2,
  "cold": false,
  "stale": false,
  "next_refresh_at": null
}

PublicNodeResponse

Field Meaning
name Worker name as in kubectl get nodes (falls back to the internal worker identity before the node joins).
pool Owning pool name, or null.
status Customer lifecycle: Pending, Provisioning, Provisioned, Running, Deleting, Deleted, Failed, Unknown.
kubernetes_version kubelet version when known.
ready Kubernetes Node Ready.
created_at / deleted_at

Addresses and provider ids are not exposed. Use kubectl if you need them on the node.

Delete a single node

DELETE /v1/public/clusters/{id}/nodes/{node_name} → 202 PublicNodeDeletePendingResponse.

The pool's declared count does not change. Door replaces the worker so the pool returns to the desired size. Use this to recycle one bad node, not to scale down (use PATCH count or autoscale for that).

node_name must match ^[a-z0-9][-a-z0-9.]{0,252}[a-z0-9]$ or you get 400. The API does not 404 on an unknown name; a bad name fails asynchronously — watch the node list.

409 if the cluster is not Provisioned or another delete for that node is already in flight.

curl -sS -X DELETE \
  "https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/nodes/payments-prod-workers-abc12" \
  -H "Authorization: Bearer $DOOR_TOKEN"
{
  "schema_version": "node.public.v1",
  "cluster_id": "3f9c1a2e-7b4d-4c58-9e21-5d6f8a0b1c2d",
  "node_name": "payments-prod-workers-abc12"
}

Poll GET …/nodes until the old name disappears and a replacement appears.

Maya: “Replace node payments-prod-workers-abc12 on payments-prod.”

Labels, tags, and root_volume_size_gib

Pool labels and tags are stored on the pool and returned by the API. They are not applied as Kubernetes node labels (workers are provisioned without extra node labels). To label nodes for scheduling, use kubectl label node (or a DaemonSet/admission policy) after the node is Ready.

root_volume_size_gib is not a request field. It is taken from the catalog tier's spec.disk_gib (for dks.c5.xlarge, 80 GiB). You cannot PATCH it. Changing disk means a new pool with a different size.

Cluster-level tags (string array on the cluster) are separate from pool tags (string maps).

Capacity totals

On GET cluster:

Field How it is computed
total_vcpu Sum of (tier vCPU × effective nodes) over alive pools. Null if a pool size cannot be resolved.
total_memory_gib Same for RAM.
total_volume_gib Sum of (root_volume_size_gib × effective nodes).
Effective nodes max_nodes when auto_scale is on and max_nodes is set; otherwise count.

Use these as planned capacity, not as a live kubectl top reading.

Status code cheat sheet

Code Typical cause
200 List pools or nodes.
202 Add/scale/remove pool or delete node accepted.
400 Unknown machine tier; invalid node name.
402 Prepaid credit cannot cover the scale.
403 Not super_admin (mutations) or not a member (reads).
404 Cluster, pool, or (after teardown) cluster gone.
409 Not Provisioned; last pool; name clash; operation in flight.
422 Empty PATCH; merged autoscaling bounds invalid.
503 Node inventory temporarily unavailable.