Skip to content

Replace the draft with a structured config.

PUT
/v1/installer/config
Code sample: Shell / cURL
curl --request PUT \
--url https://192.0.2.10:9443/v1/installer/config \
--header 'Authorization: <Authorization>' \
--header 'Content-Type: application/json' \
--header 'If-Match: "3"' \
--data '{ "mode": "single-node", "profile": "single-node", "region": "example", "admin": { "email": "admin@cloud.internal" }, "dns": { "parentDomain": "cloud.internal", "lanAddress": "example", "upstreamServers": [ "example" ], "upstreamFallback": true }, "network": { "uplinkInterface": "example", "topology": "routed-host", "floatingIpCidrs": [ "example" ], "defaultVpcCidr": "example" }, "storage": { "disks": [ { "byId": "/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_S7DNNJ0X123456", "use": "compute", "poolName": "fast", "storageClassName": "nvme" } ], "mdArrays": [ { "name": "md-data0", "level": 0, "members": [ "example" ] } ], "dataDisks": [ "example" ], "wipeConfirm": [ "example" ] }, "services": { "additionalProperty": true }, "time": { "ntpServers": [ "example" ] }, "hostReservation": { "vcpus": 1, "memoryBytes": 1 }, "advanced": { "devMode": true, "upgradesMirrorUrl": "https://mirror.example.com", "releaseToken": "rel_0123456789abcdef" }, "cluster": { "name": "lab", "plannedSize": 3 }, "networks": { "storage": { "cidr": "192.0.2.0/24", "vlan": 90, "mtu": 9000 }, "overlay": { "cidr": "192.0.2.0/24", "vlan": 90, "mtu": 9000 }, "providerVlan": 1, "vips": { "api": "192.0.2.240", "postgres": "192.0.2.241", "openbao": "192.0.2.242", "ovnNb": "192.0.2.243", "ovnSb": "192.0.2.244", "vmselect": "192.0.2.245", "providerGw": "192.0.2.246" } }, "links": [ { "name": "example", "kind": "ethernet", "interface": "bond0", "members": [ "example" ], "bond": { "mode": "active-backup", "xmitHashPolicy": "layer2", "miimonMs": 1, "lacpRate": "slow" }, "parent": "bond0", "vlanId": 1, "mtu": 1, "purposes": [ "storage" ], "addresses": [ "example" ] } ] }'

Stores the draft even when it does not validate (a form is filled in over several calls); the response carries the validation. Unknown fields are refused with 400. Refused with 409 once an install is running or done. With If-Match set to the revision the caller’s copy was read at, a draft stored since then (another tab, the CLI) is refused with 409 DRAFT_CHANGED instead of being overwritten.

If-Match
string

Example

"3"

The revision of the draft the request was built on. When it is not the draft’s current one the request is refused with 409 DRAFT_CHANGED. Absent: on PUT the draft is replaced whatever it is, and on POST /install the current draft is installed.

Media typeapplication/json

The product answers for an install. The first is mode, the install mode, which decides which of the others apply (see GET /v1/installer/options, modes). Every field maps onto node.yaml; GET /v1/installer/config.yaml shows the result.

mode is optional so that clients that predate it keep working: a config with no mode is a single-node one, as every config was before it existed. A field the mode does not list is refused with an error on that field that names the mode. A config whose mode is not available yet is refused at mode.

object
mode

How this node is installed: the first answer of stage 2. Each mode is a deployment profile of the release model and the node’s part in it.

  • single-node: one node runs everything (profile single-node; node.yaml mode: single-node).
  • hyperconverged-init: the first node of a hyperconverged cluster, the one that founds it (“HA init”; node.yaml mode: ha-init).
  • hyperconverged-join: a node that joins a hyperconverged cluster (“HA join”; node.yaml mode: join).
  • disaggregated-control-plane: a control-plane-only node that compute and storage nodes join later (profile disaggregated, a preview whose design is open,).

GET /v1/installer/options says which are available; the others are refused at mode, with the reason.

string
Allowed values: single-node hyperconverged-init hyperconverged-join disaggregated-control-plane
profile
required

The deployment profile of mode, which it must match: single-node for single-node (and for a config with no mode), hyperconverged for both hyperconverged modes, disaggregated for the disaggregated one. node.yaml has no profile; it follows from the mode.

string
Allowed values: single-node hyperconverged disaggregated
region
required

Node.yaml cluster.region. Baked into every ARN and DNS name; fixed once installed.

string
admin
required
object
email
required

The first admin’s email, node.yaml iam.admin_email. The password is generated and written to the credential file the report names. A new draft starts with admin@cloud.internal.

string

Example

admin@cloud.internal
dns
required
object
parentDomain
required

Node.yaml dns.parent_domain, the domain the install serves its names under. It need not be public, and a new draft starts with cloud.internal. It may not be, or sit under, stratum.internal or compute.internal: the control plane creates those zones itself. Avoid .local, which macOS and most Linux desktops send to multicast DNS.

string

Example

cloud.internal
lanAddress

The node’s LAN address its authoritative DNS answers on, node.yaml identity.self_ip. Defaults to the OOB NIC’s address; empty lets the install use the default route’s source address.

string
upstreamServers

The resolvers the cloud’s DNS forwards every name it does not serve to, node.yaml dns.upstream_servers: IPv4 addresses, not this node’s own. Optional: with none, tenant VMs resolve public names from the root servers, which needs DNS out to the internet; a site that only allows its own resolvers must list them. Not the host’s own resolvers (stage 1 set those), though they are often the same: hardware.resolvers lists them, as a suggestion. A new draft starts with none.

Array<string>
<= 4 items
upstreamFallback

Node.yaml dns.upstream_fallback: when every upstream server fails, tenant VMs resolve names from the root servers instead of getting a failure. Off by default: the fallback needs DNS out to the internet, hides a dead upstream, and cannot resolve names only your own resolvers serve. No effect without upstreamServers. Changeable after the install.

boolean
network
required
object
uplinkInterface
required

The NIC tenant traffic leaves through, node.yaml provider.upstream_interface. Defaults to the OOB NIC. Another NIC must already have an IPv4 address; stage 1 configures only the OOB NIC.

string
topology
required

Node.yaml provider.topology: one of the mode’s (options.modes[].topologies, with the copy in options.modes[].provider). A single node is routed-host. A founder chooses: bridged (its uplink is the link that carries provider, else the OOB NIC on networks.providerVlan; the first floating-IP range is then the VPC transit range, node.yaml network.transit_cidr) or routed-host (no provider VLAN and no provider link; the uplink is the OOB NIC, and the founder routes the floating IPs, as their only gateway).

string
Allowed values: routed-host bridged
floatingIpCidrs
required

Node.yaml network.floating_ip_cidrs. Required, at least one; route them to the node on the LAN router. A new draft starts with the range the options recommend for this node (modes[].network.floatingIpCidrs.suggested,).

Array<string>
defaultVpcCidr
required

Node.yaml network.default_vpc_cidr.

string
storage
required

The node’s disks. By default the OS disk’s free space at its end is the node’s first pool, stratum-compute, with the storage class stratum-compute, the install’s default. When stage 1 gave the OS disks whole to the system (options.modes[].storage.osDiskLayout is whole-disk,) there is no such pool: disks must have a compute disk, and the first one is the node’s first pool and its class the default. Every other disk is left as it is unless it is listed in disks (or the older dataDisks), and every listed disk is wiped. storagePlan on the draft says what the install does to each disk of the machine.

object
disks

The data disks, each with what it is for: an LVM-thin pool for instance volumes (compute), or, on a node of a cluster, a disk reserved for a later Ceph bootstrap (ceph; not on a single node,). node.yaml storage.compute_pools and storage.ceph_osd_disks. A disk appears once. Use this or dataDisks, not both. A new draft starts with none.

An entry may name an array of mdArrays instead of a disk: byId is then /dev/md/<name>, and only use: compute is accepted.

Array<object>

One data disk and what it is for. Field errors name storage.disks[i].byId, .use, .poolName and .storageClassName.

object
byId
required

The disk, by the first of its byId paths in the hardware list. It must be dataDiskEligible, and is WIPED. Or an array of storage.mdArrays, by /dev/md/<name> (compute only).

string

Example

/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_S7DNNJ0X123456
use
required

compute: the disk becomes an LVM-thin pool on this node, which holds instance volumes (instance root disks and the volumes tenants create). Name the pool and its storage class.

ceph: the disk is wiped now and reserved for Ceph. Nothing Ceph runs at install. The node records the disk as a Ceph OSD candidate, and a later Ceph bootstrap (POST /v1/admin/storage/ceph/bootstrap, or Ceph setup in the console) starts with it selected as an OSD. Only where the mode offers it (options.modes[].storage.cephOffered): Ceph needs a cluster, so a single-node install refuses it here.

string
Allowed values: compute ceph
poolName

Compute only, and required for it. The pool’s name: this node’s LVM-thin space on this disk, holding instance volumes. Pool names are unique on a node and follow the storage-pools API’s rule: 3 to 40 characters, lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit. stratum-compute, data, stratum and stratum-block are taken by the install’s own storage. node.yaml storage.compute_pools[].name.

string

Example

fast
storageClassName

Compute only. The storage class: what tenants pick when they create a volume. A class names one pool name, and the pools of that name on every node are its capacity, so one class can span pools on several nodes. Empty means the same as poolName, which is what a single node needs; a separate name keeps the class (what tenants see) apart from the pools (where it lives). Same name rule as a pool; not stratum-compute, the OS disk’s class; two pools on one node cannot share a class. node.yaml storage.compute_pools[].storage_class.

string

Example

nvme
mdArrays

Md RAID arrays to build from data disks, node.yaml storage.md_arrays. Optional. Each array is then ONE device: an entry of disks names it by /dev/md/<name> to make it a compute pool, with its own pool name and storage class; an array no entry names is built and left unused. Disks in no array stay separate disks. Its members are data disks (the rules of disks[].byId), each in at most one array and not listed in disks themselves; every member is wiped, and one that holds data must be in wipeConfirm. storagePlan lists each member (use: md-member) and each array (mdMembers). Field errors name storage.mdArrays[i].name, .level, .members and .members[k].

Array<object>

One md RAID array of data disks. level is required; a left-out level is refused, never read as 0.

object
name

The array’s name: its device is /dev/md/<name>. 3 to 32 characters, lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit; unique; not boot, pv or stratum-compute (the install’s own arrays). Empty means md-data<i>, with i the entry’s index in mdArrays.

string

Example

md-data0
level

The RAID level (options.modes[].storage.mdLevels). 1: a mirror of 2 or more members, the size of the smallest. 10: mirrored pairs, an even number of members, at least 4, half the total size. 0: striped over 2 or more members, the total size, and no redundancy (a warning).

integer
Allowed values: 0 1 10
members
required

The member disks, by the first of their byId paths in the hardware list. Wiped.

Array<string>
dataDisks
required

The older form of disks, kept for clients that predate it: whole disks (by-id paths from the hardware list) that each become a compute pool named compute-2, compute-3,…, with a storage class of the same name (node.yaml storage.compute_disks). Send an empty list when you use disks.

Array<string>
wipeConfirm
required

The listed disks (in disks or dataDisks) that hold data and may be wiped, by the same by-id path. A disk that holds data and is not listed here is refused, whether it is for compute or for Ceph. node.yaml storage.force_wipe.

Array<string>
services
required

Service key to enabled, for the switchable services the profile offers (see options). A service left out keeps its default. Disabled ones become node.yaml services.disabled.

object
key
additional properties
boolean
time

Where the node gets its time. node.yaml time.

object
ntpServers

NTP servers, as host names or IP addresses, one chrony server line each. node.yaml time.ntp_servers. A new draft starts with options.time.suggested: DHCP’s NTP servers, else the public NTP pool. Left out (no time, or no ntpServers), the install uses the same servers. An empty list is accepted, with a validation warning: the node then syncs only from NTP servers DHCP offers, and keeps its own clock when there are none (an air-gapped site with no NTP server).

Array<string>
<= 8 items
hostReservation

The host reservation the operator chose, node.yaml host_reservation. Left out, the node computes its default from its size and keeps it in step (modes[].hostReservation.default); send it only when the operator changed the value. Both values are required, between minimum and maximum, memory a whole number of MiB. Errors name hostReservation.vcpus and hostReservation.memoryBytes. Only in a mode that lists the field (single-node). The control plane takes it at the node’s first registration; after that stratumctl nodes set-reservation changes it.

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
advanced

Settings most installs leave as they are. Left out, each takes the default given below.

object
devMode

Node.yaml dev_mode: the OpenBao web UI, the substrate’s web UIs (OpenBao, VictoriaMetrics, VictoriaLogs, vmagent, OTel zPages) published on the node’s loopback, and the OpenBao root token and Postgres password shown in the admin substrate-tools response. Left out, it is the mode’s default (options.modes[].defaultDevMode,): on for single-node, off for the others; a fresh draft carries the default mode’s. The install always writes the key. Changed later by editing node.yaml, running stratum-bootstrap render-quadlets, and restarting the units it changes.

boolean
upgradesMirrorUrl

The release mirror the cluster checks for newer releases, node.yaml upgrades.mirror_url: an http(s) URL without credentials, query or fragment. A new draft starts with the default this installer was built with (options.limits.defaultUpgradesMirrorUrl). Left out, or the default, the cluster follows the default of the release it runs. Empty switches the update check off, for an offline install upgraded from media. The founder stores it once; after the install it changes with stratumctl admin upgrade mirror set.

string

Example

https://mirror.example.com
releaseToken

A release token from the Stratum Account, node.yaml upgrades.release_token: it opens the stable release channel (releases.stratum-labs.cloud), which the update check and the release downloads then read, sending the token there and to no other host. Printable ASCII without spaces. Left out or empty, the cluster reads the default mirror. The founder stores it in OpenBao at the end of the install, unless the cluster already holds one; after that it is set, replaced or removed in the console’s upgrade settings (PUT/DELETE /v1/admin/upgrades/release-token) and never shown again. The draft and config.yaml echo it to this install session only.

string
<= 512 characters

Example

rel_0123456789abcdef
cluster

The cluster itself: node.yaml cluster.name and cluster.planned_size. hyperconverged-init only.

object
name

The cluster’s name: 1 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit. Optional.

string

Example

lab
plannedSize

The planned number of control-plane members (default 3). It drives the formation findings; nothing is rendered from it. It is the install-time plan, not a cap: a cluster takes joiners up to 5 control-plane nodes.

integer
Allowed values: 3 5
networks

The cluster’s networks, defined once on the founder: node.yaml cluster.networks, cluster.vips and provider.vlan. The management network is the OOB NIC’s subnet (options.modes[].cluster.mgmt). hyperconverged-init only.

object
storage

One purpose’s network. Optional: without it the purpose rides mgmt. A link must carry it. Errors name networks.<purpose>.cidr, .vlan and .mtu.

object
cidr
required

An IPv4 network, /30 at most, overlapping no other cluster network.

string

Example

192.0.2.0/24
vlan

Its 802.1Q tag; 0 or absent is untagged. A tagged network is carried by a vlan link with this vlanId.

integer
<= 4094

Example

90
mtu

The least MTU a link carrying it must have. A link that sets none gets it.

integer
>= 1280 <= 9216

Example

9000
overlay

One purpose’s network. Optional: without it the purpose rides mgmt. A link must carry it. Errors name networks.<purpose>.cidr, .vlan and .mtu.

object
cidr
required

An IPv4 network, /30 at most, overlapping no other cluster network.

string

Example

192.0.2.0/24
vlan

Its 802.1Q tag; 0 or absent is untagged. A tagged network is carried by a vlan link with this vlanId.

integer
<= 4094

Example

90
mtu

The least MTU a link carrying it must have. A link that sets none gets it.

integer
>= 1280 <= 9216

Example

9000
providerVlan

The VLAN the VM uplink trunks (node.yaml provider.vlan); 0 is untagged. Bridged only: required (1 to 4094) when no link carries provider (the uplink is then the OOB NIC, and its untagged traffic is the node’s own); refused under routed-host.

integer
<= 4094
vips

The service VIPs (node.yaml cluster.vips), on the management network. Each must be in its subnet, not the node’s own address nor another VIP, outside the floating-IP ranges, and free: the draft’s validation probes each with ARP on the OOB NIC, and an address that answers is refused. Errors name networks.vips.<name>.

object
api
required
string

Example

192.0.2.240
postgres
required
string

Example

192.0.2.241
openbao
required
string

Example

192.0.2.242
ovnNb
required
string

Example

192.0.2.243
ovnSb
required
string

Example

192.0.2.244
vmselect
required
string

Example

192.0.2.245
providerGw

The routed-host floating-IP next hop: the LAN router routes every floating range to it, and it follows the node whose chassis holds the VPC gateways. Required when network.topology is routed-host, refused when bridged (the router is the floating IPs’ gateway there).

string

Example

192.0.2.246
links

The founder’s links beyond the OOB NIC, each with the purposes it carries: ethernet NICs, bonds of NICs, and VLANs on either (or on the OOB NIC). node.yaml network.connections, a bond’s members as their own entries. hyperconverged-init only.

The OOB NIC is not listed: it carries oob and mgmt. A purpose no link carries falls back: storage and overlay ride mgmt, and provider the OOB NIC on networks.providerVlan. At most one link per purpose. Field errors name links[i].<field>.

Array<object>
<= 32 items

One link of the founder: an ethernet NIC, a bond of NICs, or a VLAN on a NIC or a bond, and what it carries.

object
name

The NetworkManager connection id. Empty means interface.

string
kind
required
string
Allowed values: ethernet bond vlan
interface

Ethernet: the NIC, from the hardware list (not the OOB NIC). bond: the bond’s name, e.g. bond0. vlan: the VLAN interface’s name; empty means vlan<vlanId>. At most 15 characters.

string

Example

bond0
members

Bond only. The NICs it bonds, from the hardware list; each with a carrier, in no other link, not the OOB NIC.

Array<string>
bond

A bond’s options (node.yaml network.connections[].bond).

object
mode

Empty means active-backup, which needs no switch configuration.

string
Allowed values: active-backup 802.3ad
xmitHashPolicy

802.3ad only. Empty means layer3+4, which spreads Geneve tunnels over the members.

string
Allowed values: layer2 layer2+3 layer3+4 encap2+3 encap3+4
miimonMs

The link-monitor interval. Empty means 100.

integer
lacpRate

802.3ad only.

string
Allowed values: slow fast
parent

Vlan only. The NIC (the OOB NIC included) or the interface of another link it is on.

string

Example

bond0
vlanId

Vlan only. The tag; it must be the network’s (networks.<purpose>.vlan).

integer
>= 1 <= 4094
mtu

Empty means the largest MTU of the networks it carries, and for a link VLANs ride on, the largest of theirs.

integer
>= 1280 <= 9216
purposes
required

What it carries: storage, overlay, provider. provider only on an ethernet or bond link, the trunk networks.providerVlan arrives on. A link that carries nothing is only a parent of VLANs.

Array<string>
Allowed values: storage overlay provider
addresses

Its addresses, as CIDRs in the networks it carries (storage, overlay). Empty means one per carried network: its first host address. provider takes none.

Array<string>

The stored draft and its validation.

Media typeapplication/json
object
revision
required

Counts the times the draft was stored (0 for the machine’s defaults). Send it back as If-Match on PUT /v1/installer/config or /config.yaml to refuse overwriting a draft stored since this one was read.

integer
source
required

How the draft was last set.

string
Allowed values: structured yaml
config

The product answers for an install. The first is mode, the install mode, which decides which of the others apply (see GET /v1/installer/options, modes). Every field maps onto node.yaml; GET /v1/installer/config.yaml shows the result.

mode is optional so that clients that predate it keep working: a config with no mode is a single-node one, as every config was before it existed. A field the mode does not list is refused with an error on that field that names the mode. A config whose mode is not available yet is refused at mode.

object
mode

How this node is installed: the first answer of stage 2. Each mode is a deployment profile of the release model and the node’s part in it.

  • single-node: one node runs everything (profile single-node; node.yaml mode: single-node).
  • hyperconverged-init: the first node of a hyperconverged cluster, the one that founds it (“HA init”; node.yaml mode: ha-init).
  • hyperconverged-join: a node that joins a hyperconverged cluster (“HA join”; node.yaml mode: join).
  • disaggregated-control-plane: a control-plane-only node that compute and storage nodes join later (profile disaggregated, a preview whose design is open,).

GET /v1/installer/options says which are available; the others are refused at mode, with the reason.

string
Allowed values: single-node hyperconverged-init hyperconverged-join disaggregated-control-plane
profile
required

The deployment profile of mode, which it must match: single-node for single-node (and for a config with no mode), hyperconverged for both hyperconverged modes, disaggregated for the disaggregated one. node.yaml has no profile; it follows from the mode.

string
Allowed values: single-node hyperconverged disaggregated
region
required

Node.yaml cluster.region. Baked into every ARN and DNS name; fixed once installed.

string
admin
required
object
email
required

The first admin’s email, node.yaml iam.admin_email. The password is generated and written to the credential file the report names. A new draft starts with admin@cloud.internal.

string
dns
required
object
parentDomain
required

Node.yaml dns.parent_domain, the domain the install serves its names under. It need not be public, and a new draft starts with cloud.internal. It may not be, or sit under, stratum.internal or compute.internal: the control plane creates those zones itself. Avoid .local, which macOS and most Linux desktops send to multicast DNS.

string
lanAddress

The node’s LAN address its authoritative DNS answers on, node.yaml identity.self_ip. Defaults to the OOB NIC’s address; empty lets the install use the default route’s source address.

string
upstreamServers

The resolvers the cloud’s DNS forwards every name it does not serve to, node.yaml dns.upstream_servers: IPv4 addresses, not this node’s own. Optional: with none, tenant VMs resolve public names from the root servers, which needs DNS out to the internet; a site that only allows its own resolvers must list them. Not the host’s own resolvers (stage 1 set those), though they are often the same: hardware.resolvers lists them, as a suggestion. A new draft starts with none.

Array<string>
<= 4 items
upstreamFallback

Node.yaml dns.upstream_fallback: when every upstream server fails, tenant VMs resolve names from the root servers instead of getting a failure. Off by default: the fallback needs DNS out to the internet, hides a dead upstream, and cannot resolve names only your own resolvers serve. No effect without upstreamServers. Changeable after the install.

boolean
network
required
object
uplinkInterface
required

The NIC tenant traffic leaves through, node.yaml provider.upstream_interface. Defaults to the OOB NIC. Another NIC must already have an IPv4 address; stage 1 configures only the OOB NIC.

string
topology
required

Node.yaml provider.topology: one of the mode’s (options.modes[].topologies, with the copy in options.modes[].provider). A single node is routed-host. A founder chooses: bridged (its uplink is the link that carries provider, else the OOB NIC on networks.providerVlan; the first floating-IP range is then the VPC transit range, node.yaml network.transit_cidr) or routed-host (no provider VLAN and no provider link; the uplink is the OOB NIC, and the founder routes the floating IPs, as their only gateway).

string
Allowed values: routed-host bridged
floatingIpCidrs
required

Node.yaml network.floating_ip_cidrs. Required, at least one; route them to the node on the LAN router. A new draft starts with the range the options recommend for this node (modes[].network.floatingIpCidrs.suggested,).

Array<string>
defaultVpcCidr
required

Node.yaml network.default_vpc_cidr.

string
storage
required

The node’s disks. By default the OS disk’s free space at its end is the node’s first pool, stratum-compute, with the storage class stratum-compute, the install’s default. When stage 1 gave the OS disks whole to the system (options.modes[].storage.osDiskLayout is whole-disk,) there is no such pool: disks must have a compute disk, and the first one is the node’s first pool and its class the default. Every other disk is left as it is unless it is listed in disks (or the older dataDisks), and every listed disk is wiped. storagePlan on the draft says what the install does to each disk of the machine.

object
disks

The data disks, each with what it is for: an LVM-thin pool for instance volumes (compute), or, on a node of a cluster, a disk reserved for a later Ceph bootstrap (ceph; not on a single node,). node.yaml storage.compute_pools and storage.ceph_osd_disks. A disk appears once. Use this or dataDisks, not both. A new draft starts with none.

An entry may name an array of mdArrays instead of a disk: byId is then /dev/md/<name>, and only use: compute is accepted.

Array<object>

One data disk and what it is for. Field errors name storage.disks[i].byId, .use, .poolName and .storageClassName.

object
byId
required

The disk, by the first of its byId paths in the hardware list. It must be dataDiskEligible, and is WIPED. Or an array of storage.mdArrays, by /dev/md/<name> (compute only).

string
use
required

compute: the disk becomes an LVM-thin pool on this node, which holds instance volumes (instance root disks and the volumes tenants create). Name the pool and its storage class.

ceph: the disk is wiped now and reserved for Ceph. Nothing Ceph runs at install. The node records the disk as a Ceph OSD candidate, and a later Ceph bootstrap (POST /v1/admin/storage/ceph/bootstrap, or Ceph setup in the console) starts with it selected as an OSD. Only where the mode offers it (options.modes[].storage.cephOffered): Ceph needs a cluster, so a single-node install refuses it here.

string
Allowed values: compute ceph
poolName

Compute only, and required for it. The pool’s name: this node’s LVM-thin space on this disk, holding instance volumes. Pool names are unique on a node and follow the storage-pools API’s rule: 3 to 40 characters, lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit. stratum-compute, data, stratum and stratum-block are taken by the install’s own storage. node.yaml storage.compute_pools[].name.

string
storageClassName

Compute only. The storage class: what tenants pick when they create a volume. A class names one pool name, and the pools of that name on every node are its capacity, so one class can span pools on several nodes. Empty means the same as poolName, which is what a single node needs; a separate name keeps the class (what tenants see) apart from the pools (where it lives). Same name rule as a pool; not stratum-compute, the OS disk’s class; two pools on one node cannot share a class. node.yaml storage.compute_pools[].storage_class.

string
mdArrays

Md RAID arrays to build from data disks, node.yaml storage.md_arrays. Optional. Each array is then ONE device: an entry of disks names it by /dev/md/<name> to make it a compute pool, with its own pool name and storage class; an array no entry names is built and left unused. Disks in no array stay separate disks. Its members are data disks (the rules of disks[].byId), each in at most one array and not listed in disks themselves; every member is wiped, and one that holds data must be in wipeConfirm. storagePlan lists each member (use: md-member) and each array (mdMembers). Field errors name storage.mdArrays[i].name, .level, .members and .members[k].

Array<object>

One md RAID array of data disks. level is required; a left-out level is refused, never read as 0.

object
name

The array’s name: its device is /dev/md/<name>. 3 to 32 characters, lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit; unique; not boot, pv or stratum-compute (the install’s own arrays). Empty means md-data<i>, with i the entry’s index in mdArrays.

string
level

The RAID level (options.modes[].storage.mdLevels). 1: a mirror of 2 or more members, the size of the smallest. 10: mirrored pairs, an even number of members, at least 4, half the total size. 0: striped over 2 or more members, the total size, and no redundancy (a warning).

integer
Allowed values: 0 1 10
members
required

The member disks, by the first of their byId paths in the hardware list. Wiped.

Array<string>
dataDisks
required

The older form of disks, kept for clients that predate it: whole disks (by-id paths from the hardware list) that each become a compute pool named compute-2, compute-3,…, with a storage class of the same name (node.yaml storage.compute_disks). Send an empty list when you use disks.

Array<string>
wipeConfirm
required

The listed disks (in disks or dataDisks) that hold data and may be wiped, by the same by-id path. A disk that holds data and is not listed here is refused, whether it is for compute or for Ceph. node.yaml storage.force_wipe.

Array<string>
services
required

Service key to enabled, for the switchable services the profile offers (see options). A service left out keeps its default. Disabled ones become node.yaml services.disabled.

object
key
additional properties
boolean
time

Where the node gets its time. node.yaml time.

object
ntpServers

NTP servers, as host names or IP addresses, one chrony server line each. node.yaml time.ntp_servers. A new draft starts with options.time.suggested: DHCP’s NTP servers, else the public NTP pool. Left out (no time, or no ntpServers), the install uses the same servers. An empty list is accepted, with a validation warning: the node then syncs only from NTP servers DHCP offers, and keeps its own clock when there are none (an air-gapped site with no NTP server).

Array<string>
<= 8 items
hostReservation

The host reservation the operator chose, node.yaml host_reservation. Left out, the node computes its default from its size and keeps it in step (modes[].hostReservation.default); send it only when the operator changed the value. Both values are required, between minimum and maximum, memory a whole number of MiB. Errors name hostReservation.vcpus and hostReservation.memoryBytes. Only in a mode that lists the field (single-node). The control plane takes it at the node’s first registration; after that stratumctl nodes set-reservation changes it.

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
advanced

Settings most installs leave as they are. Left out, each takes the default given below.

object
devMode

Node.yaml dev_mode: the OpenBao web UI, the substrate’s web UIs (OpenBao, VictoriaMetrics, VictoriaLogs, vmagent, OTel zPages) published on the node’s loopback, and the OpenBao root token and Postgres password shown in the admin substrate-tools response. Left out, it is the mode’s default (options.modes[].defaultDevMode,): on for single-node, off for the others; a fresh draft carries the default mode’s. The install always writes the key. Changed later by editing node.yaml, running stratum-bootstrap render-quadlets, and restarting the units it changes.

boolean
upgradesMirrorUrl

The release mirror the cluster checks for newer releases, node.yaml upgrades.mirror_url: an http(s) URL without credentials, query or fragment. A new draft starts with the default this installer was built with (options.limits.defaultUpgradesMirrorUrl). Left out, or the default, the cluster follows the default of the release it runs. Empty switches the update check off, for an offline install upgraded from media. The founder stores it once; after the install it changes with stratumctl admin upgrade mirror set.

string
releaseToken

A release token from the Stratum Account, node.yaml upgrades.release_token: it opens the stable release channel (releases.stratum-labs.cloud), which the update check and the release downloads then read, sending the token there and to no other host. Printable ASCII without spaces. Left out or empty, the cluster reads the default mirror. The founder stores it in OpenBao at the end of the install, unless the cluster already holds one; after that it is set, replaced or removed in the console’s upgrade settings (PUT/DELETE /v1/admin/upgrades/release-token) and never shown again. The draft and config.yaml echo it to this install session only.

string
<= 512 characters
cluster

The cluster itself: node.yaml cluster.name and cluster.planned_size. hyperconverged-init only.

object
name

The cluster’s name: 1 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit. Optional.

string
plannedSize

The planned number of control-plane members (default 3). It drives the formation findings; nothing is rendered from it. It is the install-time plan, not a cap: a cluster takes joiners up to 5 control-plane nodes.

integer
Allowed values: 3 5
networks

The cluster’s networks, defined once on the founder: node.yaml cluster.networks, cluster.vips and provider.vlan. The management network is the OOB NIC’s subnet (options.modes[].cluster.mgmt). hyperconverged-init only.

object
storage

One purpose’s network. Optional: without it the purpose rides mgmt. A link must carry it. Errors name networks.<purpose>.cidr, .vlan and .mtu.

object
cidr
required

An IPv4 network, /30 at most, overlapping no other cluster network.

string
vlan

Its 802.1Q tag; 0 or absent is untagged. A tagged network is carried by a vlan link with this vlanId.

integer
<= 4094
mtu

The least MTU a link carrying it must have. A link that sets none gets it.

integer
>= 1280 <= 9216
overlay

One purpose’s network. Optional: without it the purpose rides mgmt. A link must carry it. Errors name networks.<purpose>.cidr, .vlan and .mtu.

object
cidr
required

An IPv4 network, /30 at most, overlapping no other cluster network.

string
vlan

Its 802.1Q tag; 0 or absent is untagged. A tagged network is carried by a vlan link with this vlanId.

integer
<= 4094
mtu

The least MTU a link carrying it must have. A link that sets none gets it.

integer
>= 1280 <= 9216
providerVlan

The VLAN the VM uplink trunks (node.yaml provider.vlan); 0 is untagged. Bridged only: required (1 to 4094) when no link carries provider (the uplink is then the OOB NIC, and its untagged traffic is the node’s own); refused under routed-host.

integer
<= 4094
vips

The service VIPs (node.yaml cluster.vips), on the management network. Each must be in its subnet, not the node’s own address nor another VIP, outside the floating-IP ranges, and free: the draft’s validation probes each with ARP on the OOB NIC, and an address that answers is refused. Errors name networks.vips.<name>.

object
api
required
string
postgres
required
string
openbao
required
string
ovnNb
required
string
ovnSb
required
string
vmselect
required
string
providerGw

The routed-host floating-IP next hop: the LAN router routes every floating range to it, and it follows the node whose chassis holds the VPC gateways. Required when network.topology is routed-host, refused when bridged (the router is the floating IPs’ gateway there).

string
links

The founder’s links beyond the OOB NIC, each with the purposes it carries: ethernet NICs, bonds of NICs, and VLANs on either (or on the OOB NIC). node.yaml network.connections, a bond’s members as their own entries. hyperconverged-init only.

The OOB NIC is not listed: it carries oob and mgmt. A purpose no link carries falls back: storage and overlay ride mgmt, and provider the OOB NIC on networks.providerVlan. At most one link per purpose. Field errors name links[i].<field>.

Array<object>
<= 32 items

One link of the founder: an ethernet NIC, a bond of NICs, or a VLAN on a NIC or a bond, and what it carries.

object
name

The NetworkManager connection id. Empty means interface.

string
kind
required
string
Allowed values: ethernet bond vlan
interface

Ethernet: the NIC, from the hardware list (not the OOB NIC). bond: the bond’s name, e.g. bond0. vlan: the VLAN interface’s name; empty means vlan<vlanId>. At most 15 characters.

string
members

Bond only. The NICs it bonds, from the hardware list; each with a carrier, in no other link, not the OOB NIC.

Array<string>
bond

A bond’s options (node.yaml network.connections[].bond).

object
mode

Empty means active-backup, which needs no switch configuration.

string
Allowed values: active-backup 802.3ad
xmitHashPolicy

802.3ad only. Empty means layer3+4, which spreads Geneve tunnels over the members.

string
Allowed values: layer2 layer2+3 layer3+4 encap2+3 encap3+4
miimonMs

The link-monitor interval. Empty means 100.

integer
lacpRate

802.3ad only.

string
Allowed values: slow fast
parent

Vlan only. The NIC (the OOB NIC included) or the interface of another link it is on.

string
vlanId

Vlan only. The tag; it must be the network’s (networks.<purpose>.vlan).

integer
>= 1 <= 4094
mtu

Empty means the largest MTU of the networks it carries, and for a link VLANs ride on, the largest of theirs.

integer
>= 1280 <= 9216
purposes
required

What it carries: storage, overlay, provider. provider only on an ethernet or bond link, the trunk networks.providerVlan arrives on. A link that carries nothing is only a parent of VLANs.

Array<string>
Allowed values: storage overlay provider
addresses

Its addresses, as CIDRs in the networks it carries (storage, overlay). Empty means one per carried network: its first host address. provider takes none.

Array<string>
validation
required
object
valid
required
boolean
errors
required
Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string
warnings
required
Array<string>
clientSetup

How the LAN would reach the cloud with this draft’s answers: the route for its floating IPs via its LAN address (else the address this service listens on), and the DNS forward for its parent domain to its LAN address. Read from the draft whether or not it validates; absent when the draft has no floating-IP range in CIDR form. dns is absent while the draft names no LAN address: the install then detects one, and the report has it.

object
routes
required

The floating-IP ranges (node.yaml network.floating_ip_cidrs, in network form), each to route via nextHop.

Array<string>
nextHop
required

The node’s LAN address, the next hop of every route.

string
dns

Conditional forwarding: send domain and every name under it to server, port port. Absent when the node serves no DNS on the LAN (no LAN address was named or detected).

object
domain
required

The parent domain (node.yaml dns.parent_domain).

string
server
required

The node’s LAN address, where its authoritative DNS answers.

string
port
required

Always 53.

integer
hosts

The hosts-file fallback, for a machine whose resolver cannot forward a domain: the front-door names, all on one address. Load balancer and instance names come and go, so they need the DNS forward. Absent with dns.

object
address
required
string
names
required
Array<string>
snippets
required

Per client OS, in this order, linux, linux-networkmanager, macos, windows, then hosts when there is a hosts entry.

Array<object>
object
client
required

linux: ip route and a systemd-resolved drop-in. linux-networkmanager: the route on the NetworkManager connection that reaches the node, so it survives a reboot, and the same drop-in. macos: route add and /etc/resolver/<domain>. windows: route -p add and an NRPT rule (Add-DnsClientNrptRule), in PowerShell as Administrator. hosts: the line to add to the hosts file.

string
Allowed values: linux linux-networkmanager macos windows hosts
title
required

A short label, e.g. “macOS”.

string
text
required

Ready to paste: one command per line, with no comment lines (zsh, the macOS shell, does not take comments typed at its prompt). For hosts, the line to add to the hosts file.

string
note

What a person should know before pasting it, e.g. how long the route lasts.

string
storagePlan

What the install would do to each disk of the machine, read from the draft whether or not it validates: the system disk, each data disk’s pool and storage class, each disk wiped and reserved for Ceph, and the disks left alone. In the hardware list’s order. Absent when the draft’s storage cannot be read (a raw node.yaml that does not parse).

Array<object>

What the install does to one disk of the machine, or to one array of storage.mdArrays. The disks come first, in the hardware list’s order, then the arrays, in the draft’s order. An array’s entry has mdMembers; its name is the array’s name and its byId its /dev/md/<name>.

object
name
required

The kernel name, e.g. nvme1n1.

string
byId

The disk’s first by-id path, when it has one.

string
sizeBytes
required

The disk’s size. For an array, its usable size: RAID 1 the smallest member, RAID 0 the members’ sum, RAID 10 half of the smallest member times their number.

integer format: int64
use
required

system: it carries the installed system. With the compute-tail layout its free space at the end becomes the first pool, stratum-compute; with whole-disk it is the system’s only and holds no pool. compute and ceph: as StorageDisk.use. md-member: a member of the array mdArray. unused: the install leaves it as it is; for an array, it is built and holds no pool.

string
Allowed values: system compute ceph md-member unused
poolName

For compute, the pool it becomes.

string
storageClassName

For compute, the pool’s storage class (its poolName when none was given).

string
mdArray

For md-member, the array the disk joins.

string
mdLevel

For an array, its RAID level.

integer
mdMembers

For an array, its members’ kernel names, e.g. [vdb, vdc].

Array<string>
wiped
required

The install erases the disk (compute, ceph and md-member; not an array, which is new).

boolean
holds
required

What is on the disk now, and so lost when it is wiped. Empty for a blank disk and for an array.

Array<string>
summary
required

One sentence for a person, e.g. “Wiped and reserved for Ceph: nothing Ceph runs at install; a later Ceph bootstrap starts with this disk selected as an OSD.”

string
defaultClass
required

Whether this disk carries the install’s default storage class: the system disk with the compute-tail layout (stratum-compute), the first compute disk with whole-disk. On a mirror both system disks are marked. False for every disk of a whole-disk draft that has no compute disk yet.

boolean
hostReservation

The draft mode’s host reservation on this machine, with the draft’s Ceph disks counted: the default the install computes when the draft sets none, and the range it may set. Absent as in the options, and for a raw node.yaml whose mode cannot be read.

object
settable
required

Whether the mode lists hostReservation among its fields.

boolean
reason

Why it is not settable, for a person. Absent when it is.

string
default
required

A host reservation: what the node keeps back from VMs for itself (the substrate, and on a hyperconverged node Ceph).

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
minimum
required

A host reservation: what the node keeps back from VMs for itself (the substrate, and on a hyperconverged node Ceph).

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
maximum
required

The node’s threads, and its memory rounded down to a whole MiB.

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
perCephOsd

A host reservation: what the node keeps back from VMs for itself (the substrate, and on a hyperconverged node Ceph).

object
vcpus
required
integer
>= 1
memoryBytes
required

A whole number of MiB.

integer format: int64
>= 4294967296
cephOsds
required

The Ceph OSDs default counts.

integer
formula
required

How the default is computed, for a person.

string

Example

{
"source": "structured",
"config": {
"mode": "single-node",
"profile": "single-node",
"admin": {
"email": "admin@cloud.internal"
},
"dns": {
"parentDomain": "cloud.internal"
},
"network": {
"topology": "routed-host"
},
"storage": {
"disks": [
{
"byId": "/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_S7DNNJ0X123456",
"use": "compute",
"poolName": "fast",
"storageClassName": "nvme"
}
],
"mdArrays": [
{
"name": "md-data0",
"level": 0
}
]
},
"advanced": {
"upgradesMirrorUrl": "https://mirror.example.com",
"releaseToken": "rel_0123456789abcdef"
},
"cluster": {
"name": "lab",
"plannedSize": 3
},
"networks": {
"storage": {
"cidr": "192.0.2.0/24",
"vlan": 90,
"mtu": 9000
},
"overlay": {
"cidr": "192.0.2.0/24",
"vlan": 90,
"mtu": 9000
},
"vips": {
"api": "192.0.2.240",
"postgres": "192.0.2.241",
"openbao": "192.0.2.242",
"ovnNb": "192.0.2.243",
"ovnSb": "192.0.2.244",
"vmselect": "192.0.2.245",
"providerGw": "192.0.2.246"
}
},
"links": [
{
"kind": "ethernet",
"interface": "bond0",
"bond": {
"mode": "active-backup",
"xmitHashPolicy": "layer2",
"lacpRate": "slow"
},
"parent": "bond0",
"purposes": [
"storage"
]
}
]
},
"clientSetup": {
"snippets": [
{
"client": "linux"
}
]
},
"storagePlan": [
{
"use": "system"
}
]
}

The request is malformed.

Media typeapplication/problem+json

RFC 7807 problem details, the control plane’s shape.

object
type
required
string
title
required
string
status
required
integer
>= 400 <= 599
detail
string
code
required
string
requestId
required
string
errors

Field errors, for INVALID_CONFIG.

Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string

Example generated

{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"code": "example",
"requestId": "example",
"errors": [
{
"field": "example",
"message": "example"
}
]
}

No install code, or the wrong one.

Media typeapplication/problem+json

RFC 7807 problem details, the control plane’s shape.

object
type
required
string
title
required
string
status
required
integer
>= 400 <= 599
detail
string
code
required
string
requestId
required
string
errors

Field errors, for INVALID_CONFIG.

Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string

Example generated

{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"code": "example",
"requestId": "example",
"errors": [
{
"field": "example",
"message": "example"
}
]
}

An install is running or has succeeded.

Media typeapplication/problem+json

RFC 7807 problem details, the control plane’s shape.

object
type
required
string
title
required
string
status
required
integer
>= 400 <= 599
detail
string
code
required
string
requestId
required
string
errors

Field errors, for INVALID_CONFIG.

Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string

Example generated

{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"code": "example",
"requestId": "example",
"errors": [
{
"field": "example",
"message": "example"
}
]
}

Too many wrong install codes; wait for Retry-After.

Media typeapplication/problem+json

RFC 7807 problem details, the control plane’s shape.

object
type
required
string
title
required
string
status
required
integer
>= 400 <= 599
detail
string
code
required
string
requestId
required
string
errors

Field errors, for INVALID_CONFIG.

Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string

Example generated

{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"code": "example",
"requestId": "example",
"errors": [
{
"field": "example",
"message": "example"
}
]
}
Retry-After
integer

Seconds until the next attempt is considered.

The installer failed to answer.

Media typeapplication/problem+json

RFC 7807 problem details, the control plane’s shape.

object
type
required
string
title
required
string
status
required
integer
>= 400 <= 599
detail
string
code
required
string
requestId
required
string
errors

Field errors, for INVALID_CONFIG.

Array<object>
object
field
required

The structured field (network.floatingIpCidrs[1]); empty for the whole config. For a raw YAML draft, the node.yaml key.

string
message
required
string

Example generated

{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"code": "example",
"requestId": "example",
"errors": [
{
"field": "example",
"message": "example"
}
]
}