Cluster lifecycle¶
This page is the create → read → follow progress → update metadata → delete loop on /v1/public/clusters. Node pools, kubeconfig, Elastic IPs, and firewall rules have their own pages; they all require Provisioned except kubeconfig (also allowed at ControlPlaneReady) and cluster delete (any phase).
Who can do this: member can list, get, and long-poll. super_admin can create, patch, change exposure, and delete.
Create¶
POST /v1/public/clusters → 201 and a PublicClusterResponse with phase=Requested. Provisioning continues asynchronously.
Console: https://door.cloud/dks/clusters/new. On success the UI opens /dks/clusters/<id>/provisioning.
Maya: “Create cluster payments-prod in Abidjan with two dks.c5.xlarge workers.” Approve the confirmation card.
PublicClusterCreateRequest¶
extra="forbid": unknown keys (including any control-plane field) → 422.
| Field | Type | Required | Default | Constraints |
|---|---|---|---|---|
name | DNS label | yes | — | ^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$, 1–63 chars. Immutable later. Unique per organization. |
organization | DNS label | yes | — | Slug or UUID. Header/query ignored on this route. Omit → 422. |
zone | string | yes | — | Must exist in the catalog (abidjan). Unknown → 400. |
kubernetes_version | string | yes | — | Catalog kubernetes_versions[].id (example v1.32.4). Unknown → 400. |
node_pools | array | yes | — | Length 1–20. Each item is a pool (below). |
exposure_mode | public | private | no | Catalog/platform default (public today). null or empty → default. Also accepted as exposureMode. | |
network_mode | intranet | internet | no | intranet. null or empty → default. Also accepted as networkMode. | Immutable after create. |
description | string or null | no | null | Max 16384 characters. |
tags | array of DNS labels | no | [] | Max 64 entries. Replaces as a JSON array of strings. |
There is no control-plane field. Door applies the control plane assigned to your organization. The zone's active catalog option is the default.
node_pools[] (PublicNodePoolRequest)¶
| Field | Type | Default | Constraints |
|---|---|---|---|
name | DNS label | default | Same pattern as cluster name. Unique among pools on the cluster. |
size | string | — | Public machine tier id (dks.c5.xlarge). Required, min length 1. Unknown → 400. |
count | int | 1 | 0–200. count=0 requires auto_scale=true. |
auto_scale | bool | false | When true, max_nodes is required. |
min_nodes | int or null | null (becomes 1 if autoscaling and omitted) | ≥ 0 |
max_nodes | int or null | null | ≥ 1; must be ≥ min_nodes; count must be inside [min_nodes, max_nodes] when autoscaling. |
labels | object string→string | {} | Stored on the pool record. |
tags | object string→string | {} | Stored on the pool record. |
Autoscaling validation errors are 422 (create body) or 422 on later PATCH.
curl -sS -X POST "https://dks-api.apps.door.cloud/v1/public/clusters" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f3a0c11-create-payments" \
-d '{
"name": "payments-prod",
"organization": "acme",
"zone": "abidjan",
"kubernetes_version": "v1.32.4",
"exposure_mode": "public",
"node_pools": [
{ "name": "workers", "size": "dks.c5.xlarge", "count": 2 }
],
"tags": ["frontend"],
"description": "Payments production"
}'
Idempotency-Key¶
Optional header on create and on cluster delete (and on pool PATCH).
| Rule | Result |
|---|---|
| Omitted | Each call is a new request. |
Format not ^[A-Za-z0-9_\-:.]{8,255}$ | 400 |
| Same key + same body, same organization | Replay of the stored response including the original status (201 for create, 202 for delete). Header Idempotency-Replay: true. |
| Same key + different body | 409 — key was previously used with a different payload. The error does not include another cluster's id. |
?force=true on delete | Counts as a different request from a non-force delete with the same key. |
Use a new key per distinct create (for example a UUID).
Create status codes¶
| Code | Meaning |
|---|---|
201 | Accepted. Body is the public cluster. Watch phase. |
400 | Unknown zone/version/tier, empty pools, or bad Idempotency-Key. |
401 | Bad or missing token. |
402 | Prepaid organization lacks remaining create credit. Contact your Door account team. |
403 | Not super_admin on the target org. |
404 | Organization not found. |
409 | Cluster name already exists; Idempotency-Key reused with a different body; or networking could not be reserved for this cluster (retry or contact support). |
422 | Schema: extra fields, bad types, invalid DNS label, autoscaling bounds. |
503 | Platform capacity exhausted. Retry later or contact support. |
Read¶
List¶
GET /v1/public/clusters
| Query | Default | Notes |
|---|---|---|
organization | resolved org | See auth. |
zone | all zones | Example abidjan. |
page | 1 | 1-indexed. |
size | 20 | Max 100. |
include_deleted | false | When true, include clusters in Deleting / Deleted that still have a row. Default list hides those. |
There is no phase query filter on this public list. Filter client-side on items[].phase.
curl -sS "https://dks-api.apps.door.cloud/v1/public/clusters?zone=abidjan&page=1&size=20" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "X-Door-Organization: acme"
Envelope: items, total, page, size, total_pages.
Console: https://door.cloud/dks/clusters.
Maya: “List my clusters in Abidjan.”
Get one¶
GET /v1/public/clusters/{id} → PublicClusterResponse. Organization comes from the row (403 if you are not a member of that org, 404 if the id does not exist).
PublicClusterResponse fields¶
| Field | Type | Meaning |
|---|---|---|
schema_version | string | cluster.public.v1 |
id | string | Cluster UUID. |
name | string | DNS name you chose. |
zone | string | Example abidjan. |
kubernetes_version | string | Example v1.32.4. |
phase | string | Public lifecycle: Requested, Provisioning, ControlPlaneReady, NodesReady, Provisioned, Failed, Deleting, Deleted. |
state_version | int or null | Monotonic cursor for /state. May be null immediately after create before the first snapshot. |
progress | object | Server-authored narration (below). |
health | ok | degraded | Collapsed health. Same value as progress.health. |
failure_message | string or null | Set when Failed (and on a failed delete still in Deleting). Customer copy only. |
hub_registration | object | Door hub registration: status pending | registered | failed, registered_at, optional reason. |
default_environment | object | Default Door environment: status pending | ready | failed, ready_at, optional reason. |
node_pools | array | Public pools (see node pools). |
total_vcpu | int or null | Planned worker vCPU (autoscaling uses max_nodes). |
total_memory_gib | int or null | Planned worker RAM. |
total_volume_gib | int | Planned worker disk. |
billable_public_ip_count | int | Internet-routable Elastic IPs on the cluster. |
exposure_mode | public | private | How the Kubernetes API is reached. |
network_mode | intranet | internet | Dataplane Elastic IP routing. |
public_exposure_fqdn | string or null | Hostname when exposure_mode=public (pattern <cluster>-<org>-<hash>.clusters.dks.door.africa). |
private_api_server_endpoint | string or null | https://<address>:6443 when private and the address exists; null when public. |
tags | string[] | Your tags. |
description | string or null | Your description. |
created_at / updated_at | datetime | |
deleted_at | datetime or null | Set as teardown completes. |
Follow progress¶
GET /v1/public/clusters/{id}/state
| Query | Default | Notes |
|---|---|---|
since | 0 | Last state_version you saw. The call blocks until a newer version exists or the timeout hits. |
timeout_seconds | 25 | 0–60. |
Response header X-State-Version equals state_version. If no snapshot exists yet: 404 no projected state for cluster … — retry GET the cluster or /state shortly after create.
Body (PublicClusterStatusResponse):
| Field | Meaning |
|---|---|
schema_version | cluster.public.v1 |
cluster_id | UUID |
state_version | Cursor for the next since |
phase | Same public lifecycle as GET cluster |
progress | Narration block |
health | ok or degraded |
operations | In-flight or recent side activities: kind, phase, progress_pct, started_at, completed_at. Lifecycle stays Provisioned while a scale runs — watch this array, do not invent a "Scaling" phase. |
progress.step is present on the create path (5 steps). It is null for Failed / Deleting / Deleted (except a failed create may still show step 3 Creating infrastructure when that is known).
Create steps (titles and messages)¶
Render title and message from the API. They are:
step.index (of 5) | title / step.label | message | Typical phase |
|---|---|---|---|
| 1 | Request received | We're preparing your cluster request. | Requested |
| 2 | Validating configuration | Checking your cluster settings and network plan. | Provisioning (early) |
| 3 | Creating infrastructure | Setting up networking and compute for your cluster. | Provisioning (later) |
| 4 | Control plane ready | Your Kubernetes API is online. You can download your kubeconfig while worker nodes finish joining. | ControlPlaneReady |
| 4 | Worker nodes ready | Worker nodes have joined. Finishing health checks and core services. | NodesReady |
| 5 | Cluster ready | Your cluster is ready. All core components are healthy. | Provisioned |
Deleting: title Deleting, message Removing your cluster's resources. This can take a few minutes. (percent null). Deleted: title Deleted, message Cluster removed. (percent 100). Failed: title Setup failed, message from the failure list below.
curl -sS \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/state?since=0&timeout_seconds=25" \
-H "Authorization: Bearer $DOOR_TOKEN"
Console: /dks/clusters/<id>/provisioning. Maya: progress card after you approve create.
Update metadata¶
PATCH /v1/public/clusters/{id} → 200. Only tags and/or description. Omitted keys stay unchanged. description: null clears it. name is not accepted (immutable). Empty body → 422. Soft-deleted cluster → 409.
curl -sS -X PATCH \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "tags": ["frontend", "prod"], "description": "Payments production" }'
To flip API exposure: PATCH /v1/public/clusters/{id}/exposure with { "exposure_mode": "public" } or "private" → 202. Then re-download kubeconfig. Transient 409 means a previous change is still applying — retry. 400 if public exposure is not available for the zone.
Console: tags/description on Overview / Settings; exposure on Access & kubeconfig. Maya: “Set description on payments-prod to Payments production.”
Delete¶
DELETE /v1/public/clusters/{id} → 202, phase=Deleting. Idempotent: repeating delete while already Deleting does not start a second teardown (unless a failed delete needs a retry).
curl -sS -X DELETE \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "Idempotency-Key: payments-prod-delete-001"
Then poll /state until Deleted. GET the id eventually returns 404. Treat that 404 as success.
| Code | Meaning |
|---|---|
202 | Teardown accepted. |
403 | Not super_admin. |
404 | Cluster already gone (or unknown id). |
409 | Door hub environments still exist. Delete those first, or pass ?force=true to tear them down with the cluster. |
502 | Hub environment teardown did not finish; cluster delete did not start. Retry. |
503 | Hub environments cannot be verified right now. Retry. |
What is removed with the cluster: node pools, worker nodes, Elastic IPs, and firewall rules attached to it. You do not delete those first. Workloads on the cluster are destroyed with it.
Console: Settings → delete. Maya: “Delete cluster payments-prod.” Approve.
Failed clusters¶
When phase=Failed, failure_message is one of:
| Message | What to do |
|---|---|
| We couldn't set up networking for this cluster in this zone. Our team has been notified — please retry or contact support. | Delete and recreate, or contact support with the cluster id. |
| Setup timed out waiting for the cluster to become healthy. You can retry, or delete and recreate. | Delete and recreate. |
| Network routing didn't finish setting up. Support can help recover this cluster. | Contact support; do not assume a recreate is enough if routing is stuck. |
Something went wrong during setup (ref <cluster-id>). Contact support if this persists. | Catch-all. Include that id when you contact your Door account team. |
There is no "retry create" verb. Delete the failed cluster, then POST again (new Idempotency-Key). A failed delete stays in Deleting with a mapped failure_message — call DELETE again after support has cleared the blockage.
Next¶
- Node pools once
Provisioned - Kubeconfig from
ControlPlaneReadyorProvisioned