CRD Reference

API reference for the Machine custom resource.

API group: unbounded-cloud.io/v1alpha3

This document describes the custom resource definitions shipped with machina: Machine and MachineOperation.

Machine

PropertyValue
KindMachine
Pluralmachines
Short namemach
ScopeCluster
Status subresourceYes

Printer columns:

NameJSON PathDescription
Host.spec.ssh.hostSSH target address
Phase.status.phaseCurrent lifecycle phase
K8s Version.spec.kubernetes.versionDesired Kubernetes version
AgestandardTime since creation

spec.ssh

SSH connection details. When ssh is nil, the machina controller skips the Machine entirely.

FieldTypeRequiredDefaultDescription
sshSSHSpecNo-SSH connection configuration.
ssh.hoststringYes-Hostname or IP, optionally with port (e.g. 1.2.3.4:2222). Port 22 is assumed when omitted.
ssh.usernamestringNo"azureuser"SSH username.
ssh.privateKeyRefSecretKeySelectorYes-Reference to a Secret containing the SSH private key. Must reside in the unbounded-system namespace.
ssh.privateKeyRef.namestringYes-Secret name.
ssh.privateKeyRef.namespacestringYes-Secret namespace (must be unbounded-system).
ssh.privateKeyRef.keystringNo"ssh-privatekey"Key within the Secret’s data map.
ssh.bastionBastionSSHSpecNo-Optional jump host for the SSH connection.
ssh.bastion.hoststringYes-Bastion hostname or IP, optionally with port.
ssh.bastion.usernamestringNo"azureuser"Bastion SSH username.
ssh.bastion.privateKeyRef*SecretKeySelectorNoSame as ssh.privateKeyRefBastion SSH key. Falls back to the parent ssh.privateKeyRef when omitted.

spec.host.netboot

Network boot configuration consumed by the Metalman controller. The released top-level spec.pxe remains a deprecated fallback for existing Machines.

FieldTypeRequiredDefaultDescription
host.netbootPXESpecNo-PXE boot configuration.
host.netboot.imagestringYes-OCI machine image reference containing /disk/disk.img.gz (e.g. "ghcr.io/azure/host-ubuntu2404:v1").
host.netboot.architecturestringNoamd64Target CPU architecture for PXE boot artifacts and machine images. Allowed values: amd64, arm64.
host.netboot.netbootImagestringNoMetalman defaultOCI netboot image reference containing PXE boot artifacts.
host.netboot.bootProtocolstringNoPXENetwork boot trigger protocol for repaves. PXE uses DHCP/TFTP bootfile options. HTTP uses Redfish UEFI HTTP boot with a URL derived from the netboot image metadata. Allowed values: PXE, HTTP.
host.netboot.dhcpLeases[]DHCPLeaseNo-Provisioning network settings. They are served as static DHCP leases during PXE boot and used for Redfish firmware, installer, NoCloud, and installed-system static configuration during HTTP boot.
host.netboot.dhcpLeases[].ipv4stringYes-Static IPv4 address to assign.
host.netboot.dhcpLeases[].macstringYes-NIC MAC address (matched case-insensitively).
host.netboot.dhcpLeases[].subnetMaskstringYes-Subnet mask.
host.netboot.dhcpLeases[].gatewaystringYes-Default gateway.
host.netboot.dhcpLeases[].dns[]stringNo-DNS server addresses.
host.netboot.targetDiskstringNoInstaller-selectedBlock device the installer writes the machine image to, such as /dev/nvme0n1 or /dev/disk/by-id/.... When omitted, the initrd selects a disk automatically.
host.netboot.redfishRedfishSpecNo-BMC access via the Redfish API.
host.netboot.redfish.urlstringYes-Redfish endpoint URL.
host.netboot.redfish.usernamestringYes-Redfish username.
host.netboot.redfish.deviceIDstringNo"1"Redfish system device ID.
host.netboot.redfish.passwordRefSecretKeySelectorYes-Secret containing the Redfish password.
host.netboot.cloudInitCloudInitSpecNo-Optional cloud-init customization for PXE-booted machines.
host.netboot.cloudInit.userDataConfigMapRefConfigMapKeySelectorNo-Reference to a ConfigMap containing custom cloud-init user-data.
host.netboot.cloudInit.userDataConfigMapRef.namestringYes-ConfigMap name.
host.netboot.cloudInit.userDataConfigMapRef.namespacestringYes-ConfigMap namespace.
host.netboot.cloudInit.userDataConfigMapRef.keystringNo"user-data"Key within the ConfigMap.

spec.kubernetes

Kubernetes join configuration.

FieldTypeRequiredDefaultDescription
kubernetesKubernetesSpecNo-Kubernetes join settings.
kubernetes.versionstringNoCluster versionDesired Kubernetes version (e.g. "v1.34.0"). A v prefix is added automatically if missing.
kubernetes.nodeRef*LocalObjectReferenceNo-Reference to the corresponding Node object. Set by the controller.
kubernetes.nodeLabelsmap[string]stringNo-Labels to apply to the Node (not yet propagated by the machina controller).
kubernetes.bootstrapTokenRef.namestringYes-Name of the bootstrap token Secret in kube-system.

spec.host

host groups host ownership and the desired host image. New Machines select at most one of netboot, azure, or external. This keeps built-in host identity on the Machine while preserving external.machineRef as an escape hatch for providers whose own CRD has meaningful schema, status, or reconciliation.

FieldTypeRequiredDefaultDescription
host.imagestringNoPreserve current imageOpaque image identifier interpreted by the selected provider.
host.netbootPXESpecFor new Metalman MachinesNetwork boot image, DHCP, Redfish, and cloud-init settings owned by Metalman.
host.azure.resourceIDstringFor Azure VMsImmutable full Azure Resource Manager VM ID. The provider is inferred as AzureVM.
host.external.providerstringFor external hostsRegistered provider controller and credential key, such as OCIInstance, ANS, or a private provider.
host.external.providerIDstringProvider-dependentOpaque provider identity; mutable only to support provider replacement handoff.
host.external.machineRefProviderMachineReferenceProvider-dependentOptional cluster-scoped provider-owned resource for rich provider state.

If spec.host.image is omitted, a HostReplace inherits MachineConfigurationVersion.spec.template.host.image. If both are omitted, the provider preserves the host’s current image. The resolved value is frozen in the MachineOperation target before provider execution. Updating desired image state does not initiate replacement; only an explicit HostReplace MachineOperation authorizes that destructive action.

The built-in Azure provider stores its single machine-specific value inline:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: worker-01
spec:
  host:
    azure:
      resourceID: /subscriptions/<subscription>/resourceGroups/<group>/providers/Microsoft.Compute/virtualMachines/worker-01

The released top-level spec.pxe, spec.provider, and spec.providerID fields remain readable as deprecated fallbacks. New host ownership cannot be mixed with those legacy fields. Migration tooling is intentionally separate.

Machine operation credentials are selected by the Machine site label. Providers that support OIDC/workload identity use WorkloadIdentity; providers or sites that need provider-specific credential material use ExternalPlugin with a referenced Secret. Custom Go controllers register the operations they support with pkg/machineops.NewProvider and declare their provider-owned resource with WithProviderMachineKind. Each operation selects either an immediate callback or long-running begin and poll callbacks, plus optional replay, replacement bootstrap, and cleanup behavior. The controller is installed with pkg/machineops/controller.AddToManager; provider code does not reconcile MachineOperation status directly. Long-running begin callbacks must be idempotent for OperationRequest.OperationUID because the controller may call them again until their operation handle has been persisted. OperationRequest contains the exact external machine resource UID and generation, resolved host image, and observed Machine generation frozen in target status. Providers receive the canonical host.external.providerID or Azure resource ID; legacy Machines continue to supply Machine.spec.providerID. Host operations targeting the same Machine are serialized.

apiVersion: unbounded-cloud.io/v1alpha3
kind: MachineOperationCredential
metadata:
  name: remote-azure
spec:
  siteName: remote
  provider: AzureVM
  auth:
    mode: WorkloadIdentity
apiVersion: unbounded-cloud.io/v1alpha3
kind: MachineOperationCredential
metadata:
  name: remote-oci
spec:
  siteName: remote
  provider: OCIInstance
  auth:
    mode: ExternalPlugin
    secretRef:
      namespace: unbounded-system
      name: remote-oci-auth

MachineOperation

PropertyValue
KindMachineOperation
Pluralmachineoperations
Short namemop
ScopeCluster
Status subresourceYes

MachineOperation is a job-like CR for discrete operations. The in-host agent handles Kubernetes node operations such as NodeReboot and agent operations such as AgentReset; machine-ops-controller handles out-of-band VM operations such as Azure VM power actions. PXE/BMC operations remain owned by metalman for now.

FieldTypeRequiredDescription
spec.machineRefstringNoTarget Machine name. Either machineRef or machineSelector must be set.
spec.machineSelectorLabelSelectorNoSelects Machines by label. Supported for agent-handled operations (NodeReboot, AgentUpgrade, AgentReset). Each matching agent independently picks up the operation. Not supported for host operations.
spec.operationKindstringYesOne of NodeReboot, AgentUpgrade, AgentReset, HostReboot, HostPowerOff, HostPowerOn, HostReplace.
spec.parametersmap[string]stringNoOperation-specific parameters.
spec.ttlSecondsAfterFinishedint32NoDelete completed or failed operations after this many seconds.
status.phasestringNoPending, InProgress, Complete, or Failed.
status.messagestringNoHuman-readable status message.
status.startedAttimeNoOperation start timestamp.
status.completedAttimeNoTerminal phase timestamp.
status.targets[]TargetStatusNoPer-Machine target status snapshot used by host operation controllers.
status.conditions[]ConditionNoOperation conditions. Completed tracks terminal state. BootLoaderDownloaded=True is latched by metalman when a target first downloads the initial PXE boot loader, usually over TFTP. BootImageWritten starts as Unknown for metalman HostReplace, transitions to False when the PXE installer requests disk.img.gz, and transitions to True when the existing /pxe/disable completion signal is received. CloudInitDone starts as Unknown, transitions to False when first-boot cloud-init starts, and transitions to True on final cloud-init success or False with reason Failed and a summarized error when cloud-init reports a failure.

AgentUpgrade is handled by the in-host agent and requires spec.parameters.downloadURL. The URL must point to an unbounded-agent release tarball; the agent stages it as the inactive blue/green daemon binary, records the previous binary as last known good, and restarts unbounded-agent-daemon.service. If systemd cannot keep the upgraded daemon running, unbounded-agent-daemon-recovery.service switches the daemon back to the last known good binary.

The Azure VM provider handles:

OperationAzure action
HostRebootVirtualMachinesClient.BeginRestart
HostPowerOffVirtualMachinesClient.BeginPowerOff
HostPowerOnVirtualMachinesClient.BeginStart
HostReplaceVirtualMachinesClient.Get, BeginDelete, then BeginCreateOrUpdate

HostReplace for AzureVM destructively replaces the VM: it reads the existing VM model, detaches NICs and data disks, deletes the VM resource, and recreates the same VM name with fresh cloud-init custom data that installs unbounded-agent. An explicit host image may be an Azure resource ID or a publisher:offer:sku:version reference; an omitted image preserves the existing image reference. The old OS disk is not reused. Operation completion means the replacement VM create operation completed; it does not mean the Kubernetes Node is Ready. The Machine controller continues tracking whether the Kubernetes Node disappears and rejoins. Configure machine-ops-controller --api-server-endpoint with an API server address reachable from replaced hosts; the generated agent bootstrap config uses that value.

This replacement flow avoids Azure standalone VM customData immutability during native reimage. It intentionally destroys host-local state on the old OS disk. The initial implementation retains the existing blocking clone-delete-create flow; a controller crash after deletion can require manual recovery because the captured VM model is not yet durably checkpointed.

The OCI instance provider handles:

OperationOCI action
HostRebootRESET
HostPowerOffSTOP
HostPowerOnSTART
HostReplaceSTOP old instance, LaunchInstance replacement, patch Machine.spec.host.external.providerID, then terminate old instance

HostReplace for OCIInstance creates a replacement instance because OCI launch user_data is immutable after instance creation. The controller stops the old instance, launches a new instance in the same availability domain, subnet, shape, and fault domain, requests a public IP for bootstrap egress, patches Machine.spec.host.external.providerID to the new instance OCID after the replacement reaches RUNNING, and then terminates the old instance. The replacement reuses the original Machine name as the kubelet node name so it rejoins through the existing Kubernetes Node object. Operation completion means the replacement is running, provider ID handoff succeeded, and old-instance cleanup succeeded; it does not wait for the Kubernetes Node to become Ready.

The OCI replacement flow copies display name, defined tags, freeform tags, selected agent/availability/shape settings, and primary VNIC subnet/NSG/source-destination-check settings. It adds Unbounded freeform tags for idempotent retry lookup. It does not preserve the exact private IP, boot volume, or attached data volumes; active attached data volumes fail the operation before the old instance is stopped. An omitted host image preserves the source instance image. spec.parameters.imageID remains as a temporary compatibility override, while new callers should use the Machine or MachineConfiguration host image. Set spec.parameters.sshAuthorizedKeys to append SSH authorized keys to replacement metadata for break-glass debugging.

Metalman handles bare-metal host operations for Machines with spec.host.netboot.redfish (or deprecated spec.pxe.redfish) and no external host owner. Bare-metal host operations may target one Machine with spec.machineRef or a site-scoped set of Machines with spec.machineSelector. Selector-based bare-metal host operations must select a single metalman site with unbounded-cloud.io/site=<site>.

For host operations, status.targets[] is snapshotted when execution starts and remains authoritative even if labels later change. Metalman records its state-machine progress directly in each target. Resumable external providers also store the provider operation handle on the target so polling can continue after a controller restart. Each entry includes:

FieldTypeDescription
machineRefstringTarget Machine name.
phasestringTarget phase: Pending, InProgress, Complete, or Failed.
stagestringTarget operation stage such as WaitingOff, WaitingOn, or WaitingRepave.
messagestringHuman-readable target progress or failure message.
startedAttimeTarget start timestamp.
completedAttimeTarget terminal timestamp.
observedGenerationint64Machine generation acted on.
input.providerRefProviderMachineSnapshotProvider resource group, kind, name, UID, and generation frozen before execution.
input.hostImagestringResolved provider-interpreted image frozen for HostReplace; empty means preserve the current image.
attemptsint32External action attempts for retryable Redfish operations.
lastAttemptAttimeMost recent external action attempt timestamp.
providerOperationProviderOperationStatusResumable external operation metadata, including provider, operation ID, and an opaque non-secret resume token.

status

FieldTypeDescription
phasestringCurrent lifecycle phase (see table below).
messagestringHuman-readable status message.
ssh.fingerprintstringSSH host key fingerprint (not yet implemented).
redfish.certFingerprintstringBMC TLS certificate SHA-256 fingerprint. Set by metalman using TOFU.
tpm.ekPublicKeystringTPM endorsement key in PEM format. Set by metalman attestation using TOFU.
conditions[]ConditionStandard Kubernetes conditions (see below).

Conditions

TypeSet ByDescription
SSHReachablemachinaTrue / False based on a TCP probe to the SSH port.
ProvisioningmachinaTrue while the install script is running over SSH. lastTransitionTime records when provisioning started, used to detect stale provisioning attempts (e.g. after a controller restart).
ProvisionedmachinaTrue after successful SSH provisioning. ObservedGeneration tracks the spec generation.
CloudInitDonemetalmanObserved first-boot cloud-init result for PXE machines. Metalman also mirrors cloud-init progress to active HostReplace MachineOperation conditions.

Phase lifecycle

The machina controller drives the following phases:

PhaseMeaningRequeue interval
PendingSSH is unreachable.30 s
ProvisioningInstall script is running over SSH.-
JoiningProvisioned; waiting for a Node with the matching label.30 s
ReadyNode exists, or no kubernetes spec is present.5 min
FailedProvisioning encountered an error.60 s
RebootingReserved for metalman or provider controllers.-

Labels and annotations

Labels:

LabelApplied toDescription
unbounded-cloud.io/machineNodeMaps the Node back to its Machine CR. Set during provisioning.
unbounded-cloud.io/siteMachineScopes a metalman instance to a subset of Machines.
unbounded-cloud.io/default-bootstrap-tokenSecretMarks a Secret as the default bootstrap token for auto-discovery.

Annotations:

AnnotationDescription
unbounded-cloud.io/providerAssociates a Machine with a provider controller (extension point).

Examples

Minimal SSH-only Machine:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: worker-01
spec:
  ssh:
    host: "10.0.0.50"
    privateKeyRef:
      name: ssh-key
      namespace: unbounded-system
  kubernetes:
    version: v1.34.0
    bootstrapTokenRef:
      name: bootstrap-token-abc123

SSH with bastion:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: worker-02
spec:
  ssh:
    host: "192.168.1.100:2222"
    username: ubuntu
    privateKeyRef:
      name: ssh-key
      namespace: unbounded-system
      key: id_ed25519
    bastion:
      host: "bastion.example.com"
      username: jump
  kubernetes:
    version: v1.34.0
    bootstrapTokenRef:
      name: bootstrap-token-abc123

Azure VM with external power operations:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: azure-worker-01
spec:
  host:
    azure:
      resourceID: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-workers/providers/Microsoft.Compute/virtualMachines/azure-worker-01
  configurationRef:
    name: azure-workers
apiVersion: unbounded-cloud.io/v1alpha3
kind: MachineOperation
metadata:
  name: azure-worker-01-hardreboot
spec:
  machineRef: azure-worker-01
  operationKind: HostReboot
  ttlSecondsAfterFinished: 300

OCI instance with external power operations:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: oci-worker-01
spec:
  host:
    external:
      provider: OCIInstance
      providerID: oci://ocid1.instance.oc1...
  configurationRef:
    name: oci-workers
apiVersion: unbounded-cloud.io/v1alpha3
kind: MachineOperation
metadata:
  name: oci-worker-01-poweroff
spec:
  machineRef: oci-worker-01
  operationKind: HostPowerOff
  ttlSecondsAfterFinished: 300

PXE / bare-metal Machine:

apiVersion: unbounded-cloud.io/v1alpha3
kind: Machine
metadata:
  name: baremetal-01
  labels:
    unbounded-cloud.io/site: lab
spec:
  ssh:
    host: "10.0.0.60"
    privateKeyRef:
      name: ssh-key
      namespace: unbounded-system
  host:
    netboot:
      image: ghcr.io/azure/host-ubuntu2404:v1
      architecture: amd64
      dhcpLeases:
      - ipv4: "10.0.0.60"
        mac: "aa:bb:cc:dd:ee:ff"
        subnetMask: "255.255.255.0"
        gateway: "10.0.0.1"
        dns:
        - "8.8.8.8"
      redfish:
        url: "https://bmc-01.example.com"
        username: admin
        passwordRef:
          name: bmc-password
          namespace: unbounded-system
      cloudInit:
        userDataConfigMapRef:
          name: my-cloud-init
          namespace: unbounded-system
  kubernetes:
    version: v1.34.0
    bootstrapTokenRef:
      name: bootstrap-token-abc123

PXE OCI Images

Metalman uses a machine image and a netboot image for PXE repaves. The machine image is referenced by spec.host.netboot.image and contains /disk/disk.img.gz. The netboot image is referenced by spec.host.netboot.netbootImage, or by Metalman’s default when that field is omitted, and contains the reusable PXE boot environment. spec.host.netboot.architecture selects the OCI platform manifest to pull for both images and defaults to amd64.

Both images are standard OCI container images built FROM scratch with artifacts under /disk/. This follows the kubevirt containerDisk convention.

Files with a .tmpl suffix in the netboot image are Go templates rendered per-machine at serve time; other files are served verbatim. A metadata.yaml file in the netboot image provides image-level configuration such as dhcpBootImageName and httpBootPath.

Image layout

Netboot OCI image filesystem layout under /disk/: shimx64.efi, grubx64.efi, vmlinuz, initrd, init.cpio, unbounded-agent, metadata.yaml, grub/grub.cfg.tmpl, cloud-init templates

Template data

Templates receive the following data object:

FieldTypeDescription
.Machine*MachineThe Machine CR that initiated the request.
.BootLease*DHCPLeaseThe DHCP lease matching the request source IP, or the first lease when no match is available. Netboot templates use this to pass the provisioning NIC MAC, static IP, gateway, and DNS to the installer and NoCloud network configuration.
.ApiserverURLstringExternal Kubernetes API server URL.
.ServeURLstringExternal metalman HTTP URL.
.KubernetesVersionstringResolved Kubernetes version for the machine.
.ClusterDNSstringCluster DNS service IP.

The default netboot template passes .BootLease.MAC as unbounded.boot_mac. The installer initrd uses that MAC address to configure the provisioning interface instead of relying on kernel interface names such as eth0. If spec.host.netboot.targetDisk is set, the template passes it as unbounded.disk; otherwise the installer falls back to automatic disk selection.

Building images

Images are built, tagged, and pushed using standard container tooling:

docker build -t ghcr.io/azure/host-ubuntu2404:v1 -f images/host-ubuntu2404/Containerfile .
docker build -t ghcr.io/azure/netboot:v1 -f images/netboot/Containerfile .
docker push ghcr.io/azure/host-ubuntu2404:v1
docker push ghcr.io/azure/netboot:v1

See images/host-ubuntu2404/ for a machine image Containerfile and images/netboot/ for the reusable netboot image Containerfile.

metadata.yaml

dhcpBootImageName: shimx64.efi
httpBootPath: shimx64.efi

The dhcpBootImageName field specifies the boot filename included in DHCP responses (option 67) for spec.host.netboot.bootProtocol: PXE.

The httpBootPath field specifies the file path, relative to metalman’s HTTP artifact server, used for spec.host.netboot.bootProtocol: HTTP. If httpBootPath is omitted, metalman falls back to dhcpBootImageName for the UEFI HTTP boot URL.


CRD relationships

Machine CRD relationships: Machine spec fields reference OCI Image, Secrets in unbounded-system and kube-system namespaces, with bidirectional Machine-Node link via label

See Also

  • SSH Guide – SSH provisioning walkthrough using these CRDs.
  • PXE Guide – Bare-metal provisioning walkthrough using Machine and OCI netboot images.
  • Networking CRDs – Site, GatewayPool, and related CRDs from unbounded-net.
  • CLI Reference – The kubectl unbounded commands that create these resources.
  • Architecture – How these CRDs drive the provisioning pipelines.