Getting started¶
This walkthrough creates one cluster in about 15 minutes, reaches Provisioned, runs kubectl get nodes, deploys a sample app, and deletes the cluster.
Who can do this: super_admin on the organization to create and delete. member can read the catalog, follow progress, and download kubeconfig (unless your account team has tightened kubeconfig to super_admin).
Role and credit
Create and delete return 403 unless you are super_admin on the organization. A 402 on create or scale means the organization's prepaid credit does not cover the new cluster or the extra nodes: top up, or contact your Door account team. This guide does not publish prices.
Replace acme with your organization slug. Use a unique cluster name; names are unique per organization.
How to obtain $DOOR_TOKEN is described in Authentication and organizations. The console and Maya use your Door sign-in session; you only need the token for curl.
1. Pick the organization¶
Confirm Door knows which organization you are acting as.
- Console: sign in at https://door.cloud, select the organization from the account menu, then open Clusters.
- API: send
X-Door-Organization: acmeor?organization=acmeon reads. Create requires"organization": "acme"in the JSON body (header/query are ignored on create). Omitting it is422. List/catalog without header, query, or a preferred organization returns400. - Maya: work from a chat opened while that organization is selected in the console.

Clusters page of the demo organization (clusters cp-canary and cp-canary-in). Your list shows your own clusters, for example payments-prod.
2. Read the catalog¶
Always read the catalog for the zone before you create. Machine tier ids and Kubernetes versions come from here.
Console: the create wizard (/dks/clusters/new) loads this for you.
API:
curl -sS "https://dks-api.apps.door.cloud/v1/catalog?zone=abidjan" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "X-Door-Organization: $ORG"
Trimmed shape (Abidjan):
{
"schema_version": "catalog.public.v1",
"zone": { "id": "abidjan", "display_name": "Abidjan", "region_label": "Côte d'Ivoire" },
"kubernetes_versions": [
{ "id": "v1.32.4", "label": "1.32.4", "recommended": true, "support_channel": "stable" }
],
"machine_tiers": [
{ "id": "dks.c5.xlarge", "display_name": "c5.xlarge", "recommended": true,
"spec": { "vcpu": 4, "memory_gib": 8, "disk_gib": 80 } }
],
"defaults": { "network_mode": "intranet", "exposure_mode": "public" },
"constraints": {
"cluster_name": { "pattern": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$", "min_length": 1, "max_length": 63 },
"node_pools": { "min": 1, "max": 20, "min_nodes_per_pool": 1, "max_nodes_per_pool": 200 }
}
}
Full field reference: Catalog, zones, versions, and machine tiers.
Maya: “What Kubernetes versions and machine tiers can I use in Abidjan?”
3. Create the cluster¶
This example uses one worker pool named workers, tier dks.c5.xlarge (recommended on Abidjan), Kubernetes v1.32.4, and public API exposure.
Console¶
-
Open https://door.cloud/dks/clusters/new (+ New Cluster).

Demo organization screenshot (cluster
cp-canary). Type your own values from the next step. -
Set name
payments-prod, zoneabidjan, versionv1.32.4. -
Add a pool: name
workers, sizedks.c5.xlarge, count2.
Node pools step, demo organization. Use pool
workers, tierdks.c5.xlarge, count2for this walkthrough. -
Leave exposure at the catalog default (
public) unless you need a private API. - Create. The console opens the Provisioning page at
/dks/clusters/<id>/provisioning.
API¶
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: payments-prod-create-001" \
-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": "First DKS cluster"
}'
201 returns the public cluster body. phase starts at Requested. Save id:
Trimmed 201 body:
{
"schema_version": "cluster.public.v1",
"id": "3f9c1a2e-7b4d-4c58-9e21-5d6f8a0b1c2d",
"name": "payments-prod",
"zone": "abidjan",
"kubernetes_version": "v1.32.4",
"phase": "Requested",
"progress": {
"lifecycle": "Requested",
"step": { "index": 1, "total": 5, "label": "Request received" },
"title": "Request received",
"message": "We're preparing your cluster request.",
"percent": 20,
"health": "ok"
},
"node_pools": [
{ "name": "workers", "size": "dks.c5.xlarge", "count": 2, "auto_scale": false }
],
"exposure_mode": "public",
"network_mode": "intranet"
}
Status codes you may see instead: 400 unknown version or machine tier; 402 insufficient prepaid credit; 403 not super_admin; 409 name already used, or the same Idempotency-Key with a different body; 422 extra/unknown fields (including any control-plane field).
Send Idempotency-Key (8–255 characters: letters, digits, _, -, :, .). The same key plus the same body replays the stored 201. A different body with that key returns 409.
Maya¶
Ask: “Create a cluster named payments-prod in Abidjan, Kubernetes 1.32.4, one worker pool of two dks.c5.xlarge nodes, public API.”
Approve the confirmation card before Maya proceeds.
4. Follow provisioning¶
Wait until phase is Provisioned. Typical path: Requested → Provisioning → ControlPlaneReady → NodesReady → Provisioned.
Console: stay on /dks/clusters/<id>/provisioning. The step titles come from the API.
API — long-poll (blocks up to 25 seconds by default, max 60):
SINCE=0
while true; do
RESP=$(curl -sS -D /tmp/dks-headers \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/state?since=${SINCE}&timeout_seconds=25" \
-H "Authorization: Bearer $DOOR_TOKEN")
echo "$RESP" | jq '{phase, progress, state_version}'
PHASE=$(echo "$RESP" | jq -r .phase)
SINCE=$(echo "$RESP" | jq -r .state_version)
case "$PHASE" in
Provisioned) break ;;
Failed) echo "$RESP" | jq .progress.message; exit 1 ;;
esac
done
The X-State-Version response header matches state_version. Pass it as the next since.
Maya: after you approve create, a progress card streams until Provisioned, Failed, or Deleted.
If phase is Failed, read failure_message, then delete and recreate or contact your Door account team with the cluster id.
5. Download kubeconfig¶
Kubeconfig is served when the cluster is ControlPlaneReady or Provisioned. For this first walkthrough, wait until Provisioned so kubectl get nodes works (node inventory returns 409 before Provisioned).
Console: cluster → Access & kubeconfig (/dks/clusters/<id>/access-kubeconfig) → download.
API (default format=plain is a YAML file):
curl -sS -o payments-prod.kubeconfig \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}/kubeconfig" \
-H "Authorization: Bearer $DOOR_TOKEN"
chmod 600 payments-prod.kubeconfig
export KUBECONFIG="$PWD/payments-prod.kubeconfig"
If the API returns 202, wait Retry-After seconds (also retry_after_seconds in the JSON body) and retry. 409 means the cluster is not yet in ControlPlaneReady or Provisioned.
Maya: “Download the kubeconfig for payments-prod.” Treat the file as a secret; do not paste it into chat if you can use the console or API instead.
6. Confirm nodes¶
You should see two workers from the workers pool, Ready.
7. Deploy a sample app¶
kubectl create deployment hello --image=nginx:1.27 --replicas=2
kubectl expose deployment hello --port=80 --type=ClusterIP
kubectl get pods,svc
This stays inside the cluster. Opening the app on the Internet is a later step (Service, Ingress, or an Elastic IP on a Provisioned cluster).
8. Clean up¶
Deleting a cluster also removes its node pools, Elastic IPs, and firewall rules. If Door hub environments are still attached, delete returns 409 unless you add ?force=true (or remove those environments first).
Console: cluster → Settings → delete, then confirm.
API:
curl -sS -X DELETE \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}" \
-H "Authorization: Bearer $DOOR_TOKEN"
202 with phase=Deleting. Poll GET …/state until Deleted, then GET …/{id} returns 404.
curl -sS -o /dev/null -w "%{http_code}\n" \
"https://dks-api.apps.door.cloud/v1/public/clusters/${CLUSTER_ID}" \
-H "Authorization: Bearer $DOOR_TOKEN"
# eventually: 404
Maya: “Delete cluster payments-prod.” Approve the confirmation card.
Unset the kubeconfig when you are done:
Copy-paste session¶
One block from catalog through delete. Replace the token. Cluster name must be unique in the organization.
set -euo pipefail
export DOOR_TOKEN="<token from Door sign-in or your account team>"
export ORG=acme
export DKS_API=https://dks-api.apps.door.cloud
curl -sS -H "Authorization: Bearer $DOOR_TOKEN" \
-H "X-Door-Organization: $ORG" \
"$DKS_API/v1/catalog?zone=abidjan" | jq '{zone: .zone, kubernetes_versions, machine_tiers: [.machine_tiers[] | {id, recommended}]}'
CREATE=$(curl -sS -X POST "$DKS_API/v1/public/clusters" \
-H "Authorization: Bearer $DOOR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: payments-prod-create-001" \
-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}
]
}')
echo "$CREATE" | jq '{id, name, phase, progress}'
export CLUSTER_ID=$(echo "$CREATE" | jq -r .id)
SINCE=0
while true; do
BODY=$(curl -sS -H "Authorization: Bearer $DOOR_TOKEN" \
"$DKS_API/v1/public/clusters/$CLUSTER_ID/state?since=$SINCE&timeout_seconds=25")
echo "$BODY" | jq '{phase, state_version, title: .progress.title}'
PHASE=$(echo "$BODY" | jq -r .phase)
SINCE=$(echo "$BODY" | jq -r .state_version)
case "$PHASE" in
Provisioned) break ;;
Failed) echo "$BODY" | jq -r .progress.message; exit 1 ;;
esac
done
for i in $(seq 1 20); do
CODE=$(curl -sS -o payments-prod.kubeconfig -w '%{http_code}' \
-H "Authorization: Bearer $DOOR_TOKEN" \
"$DKS_API/v1/public/clusters/$CLUSTER_ID/kubeconfig?format=plain")
[ "$CODE" = "200" ] && break
[ "$CODE" = "202" ] || [ "$CODE" = "409" ] || { echo "kubeconfig HTTP $CODE"; exit 1; }
sleep 5
done
chmod 600 payments-prod.kubeconfig
export KUBECONFIG="$PWD/payments-prod.kubeconfig"
kubectl get nodes
curl -sS -X DELETE -H "Authorization: Bearer $DOOR_TOKEN" \
-H "Idempotency-Key: payments-prod-delete-001" \
"$DKS_API/v1/public/clusters/$CLUSTER_ID" | jq '{id, phase}'
while true; do
CODE=$(curl -sS -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer $DOOR_TOKEN" \
"$DKS_API/v1/public/clusters/$CLUSTER_ID")
[ "$CODE" = "404" ] && { echo "cluster deleted"; break; }
sleep 10
done
unset KUBECONFIG
rm -f payments-prod.kubeconfig