Providers

Azure Provider

Azure Provider

#Coordinator continuation rollout

The coordinator has an opt-in durable creation path for native Windows normal/amd64, managed OS disks, and VM-image-based ordinary/fixed-ID leases. Enable new admissions only with CRABBOX_DURABLE_PROVISIONING_ADMISSION=true and a stable, suitable existing CRABBOX_SESSION_SECRET; both prerequisites are reported by coordinator readiness. The gate defaults to off. Promoted OS disk snapshots, explicit snapshots/copy disks, ephemeral disks and WSL2 stay on their existing paths, including when a default resolves to one of them. Plans are bounded to 64 resolved region/SKU/market candidates. Each normal Windows class currently has five SKU candidates, so six regions with spot/on-demand fallback fit. Larger configured expansions remain on the existing path and are not advertised as resumable; readiness evaluates the effective defaults.

The durable plan freezes region/SKU/market ordering, candidate names, subscription/resource group, exact marketplace image versions, resource tags, network settings, bootstrap bytes and the extension timestamp before allocation. Subsequent deployments do not rebuild that plan from changed defaults. VM PUTs use If-None-Match: * with Compute API 2024-07-01; network/disk writes are not assumed to support create-only conditions. A missing network resource after an ambiguous dispatch remains unresolved. Fallback waits for definitive rejection or settled allocation plus verified owned cleanup, rather than starting a second candidate merely because a timeout elapsed.

Shared infrastructure writes are fenced by exact provider scope. An unresolved shared-infrastructure write retains that fence instead of authorizing another writer. Windows continuation observes the original VM's CustomData.bin and matching extension; a succeeded extension is not PUT again. Protected replay material is stored once outside public lease records, encrypted with a provisioning-specific derivation and lease/operation/generation/scope binding.

This is not migration of existing interrupted creates, universal Azure/provider resumability, or proof of live recovery. Roll out journal-aware scheduling and cleanup readers before enabling admission, and do not roll back below that version while operations or cleanup debt remain. Deployment interruption plus SSH/immutable-identity/owned-cleanup proof still requires a separately authorized isolated deployment and a fresh test lease.

Read this when you are:

  • choosing provider: azure;
  • debugging Azure VM capacity, quotas, images, or SSH readiness;
  • changing internal/providers/azure or the direct Azure provisioning code.

Azure is a managed provider for Linux, native Windows, and Windows WSL2 leases. Azure provisions the VM, public IP, NIC, and OS disk; Crabbox then owns SSH readiness, optional desktop/VNC or WSL2 bootstrap, sync, command execution, test results, and cleanup.

#When to use Azure

Reach for Azure when your cloud capacity lives in an Azure subscription, or when Microsoft tooling, Entra ID, or Azure-specific networking constraints make AWS or Hetzner a poor fit. Use Hetzner for cheaper Linux-only capacity and AWS for macOS targets — Azure does not provision macOS.

For Windows and Windows WSL2, Azure is a good default when the subscription has healthy credits or reserved capacity. Brokered Azure leases use the same ordered capacity-region fallback model as AWS.

Azure supports both execution modes:

  • Direct mode uses local Azure credentials and talks to Azure directly from the CLI.
  • Brokered mode routes lease lifecycle through the coordinator, which holds an operator-owned Azure service principal. The CLI still does SSH, sync, and command execution directly to the runner host.

#Rejected VM creates and pending cleanup

The coordinator creates the lease public IP and NIC before submitting the VM. The resource group, VNet and NSG are shared infrastructure and remain in place. Snapshot-based creation can also create an OS disk before the VM request; ordinary managed-image creation asks Azure to create that disk with the VM.

Structured ARM rejections such as SkuNotAvailable, InvalidParameter, QuotaExceeded, quota-related OperationNotAllowed, and InvalidTemplate prove the VM request was rejected. The coordinator still verifies and deletes its exact owned companion resources before reporting no remaining allocation. Failed cleanup retains the attempt's name, scope and durable immutable resource identities for retry; timeouts, lost replies and server errors do not establish rejection.

Older legacy attempts can retain uncertainty after successful rollback. Once the provider call has settled, interrupted-provisioning recovery waits five minutes, then requires 30 minutes of empty provider inventory, checking on five-minute retries. keep=true does not block this recovery after explicit deletion or failed provisioning. Inventory failures or conflicting ownership keep cleanup unresolved. New definite rejections preserve successful rollback evidence across SKU, market and region fallback and avoid this absence-confirmation delay.

Use crabbox status --provider azure --id <lease> --json to inspect cleanupStatus, cleanupRetryAt, and provisioningResourceMayExist. After cleanupStatus=complete, repeat crabbox stop --provider azure --id <lease> to finish local claim and SSH-artifact cleanup. A released state alone is not confirmation that cleanup finished.

#Commands

crabbox warmup --provider azure --class beast
crabbox warmup --provider azure --arch arm64 --class fast
crabbox warmup --provider azure --class beast --azure-os-disk ephemeral
crabbox run --provider azure --class standard -- pnpm test
crabbox run --provider azure --azure-backend dynamic-sessions -- pnpm test
crabbox warmup --provider azure --target windows --class standard
crabbox warmup --provider azure --target windows --desktop --class standard
crabbox warmup --provider azure --target windows --windows-mode wsl2 --class standard
crabbox warmup --provider azure --desktop --browser
crabbox ssh --provider azure --id blue-lobster
crabbox stop --provider azure blue-lobster
crabbox cleanup --provider azure

--type is exact (for example --type Standard_D32ads_v6). Use --class when you want SKU fallback. Azure leases use managed OS disks by default, so native checkpoint/fork works without extra flags. Pass --azure-os-disk ephemeral only for stateless leases that do not need native checkpoint/fork. This mode enables generally available full caching and requires at least eight vCPUs.

#Fixed operation IDs

Direct VM leases accept warmup --lease-id cbx_<12 lowercase hex> without a coordinator. Repeat the same command to recover the same VM after a lost reply or SSH-readiness failure. The local durable claim binds the subscription, resource group, create inputs, per-lease SSH key, allocation nonce, and observed immutable VM ID. Changed inputs or a replaced VM are rejected. Keep the local claim and SSH key until cleanup completes.

Fixed creates submit one SKU in the configured location (the class's preferred SKU unless --type is explicit). They do not perform SKU, market, or region fallback, and do not roll back an ambiguous allocation. VM creation is create-only. Replay observes the original attempt and never submits a second allocation. This supports VM-image leases, including native Windows and WSL2; snapshot forks are not supported on this direct fixed-ID path. Full-caching ephemeral disks support fixed IDs through create-only SDK requests. Ordinary direct leases retain their existing fallback behavior.

Use stop --provider azure <lease-id> for owned cleanup. Successful deletion retains a terminal tombstone; the ID cannot create another VM. If a failed attempt has not produced an identifiable VM, Crabbox retains the claim and key and refuses both replacement and unproven deletion. Inspect the named Azure resources and resolve the incomplete attempt before removing recovery state.

Before deleting a fixed VM, Azure cleanup durably records its exact NIC, public-IP, and disk identities. A retry can finish companion-resource cleanup after the VM disappears, including when the claim was explicitly recovered.

#Backend selection

azure.backend (CLI --azure-backend, env CRABBOX_AZURE_BACKEND) selects the Azure-family backend:

  • vm (default) — Azure Virtual Machines with SSH leases. This is what this page documents.
  • dynamic-sessions — routes to the azure-dynamic-sessions provider for delegated Linux runs inside an Azure Container Apps Dynamic Sessions pool.

#Config

provider: azure
target: linux
architecture: amd64
class: beast
azure:
  backend: vm
  subscriptionId: 00000000-0000-0000-0000-000000000000
  tenantId: 00000000-0000-0000-0000-000000000000
  clientId: 00000000-0000-0000-0000-000000000000
  location: eastus
  resourceGroup: crabbox-leases
  image: Canonical:ubuntu-26_04-lts:server:latest
  osDisk: managed
  vnet: crabbox-vnet
  subnet: crabbox-subnet
  nsg: crabbox-nsg
  sshCIDRs: []
  network: public

subscriptionId, tenantId, and clientId may be set in config or sourced from environment variables. The client secret is never read from config — it must come from the environment.

Set architecture: arm64 or pass --arch arm64 for Linux ARM leases. Crabbox then switches class fallback to Azure Cobalt Dpsv6/Dpdsv6 sizes and uses the matching Ubuntu ARM64 Marketplace image unless azure.image is explicitly set. Native Windows ARM64 leases also use Azure Cobalt VM sizes and require an explicit ARM64 Windows Marketplace or custom image. Azure Windows ARM64 WSL2 is not supported because those VM sizes do not support nested virtualization.

azure.network selects which IP the CLI uses for SSH: public (default) uses the VM public IP, private uses the NIC private IP from the vnet. Use private when connecting through a VPN to the Azure virtual network.

azure.osDisk accepts managed, ephemeral, or auto:

  • managed (default) provisions a managed StandardSSD_LRS OS disk so Azure native disk-snapshot checkpoints work.
  • ephemeral enables GA full caching with Compute API 2026-04-01. It requires ephemeral OS support, at least eight active vCPUs, and local storage greater than twice the OS disk size plus 1 GiB. Supported families are N/L/M/H, D/DC/E/Eb/EC v5-v7, and F v6-v7. Azure validates image capacity. Crabbox filters known unsupported class fallbacks. Native checkpoint/fork remains unavailable.
  • auto is accepted for compatibility and resolves to managed.

ephemeral-preview is removed; replace it with ephemeral. Existing ephemeral settings now enable full caching, so smaller VMs and Fsv2 no longer qualify. Constrained sizes use the active count (Standard_E8-4ds_v5 has four vCPUs). Choose an eligible --type or use --azure-os-disk managed to retain smaller VMs. Older CLIs sending ephemeral-preview to an updated coordinator receive an explicit HTTP 400 replacement error before allocation. Update CRABBOX_AZURE_OS_DISK on the coordinator too if it still names the removed mode. See Microsoft's full caching prerequisites.

#Environment variables

For direct VM leases, --azure-user-assigned-identity-resource-id (or azure.userAssignedIdentityResourceId in config) attaches one existing user-assigned managed identity. Crabbox verifies the attachment in Azure's VM identity field before a fixed lease becomes ready or can be adopted. The allocator must have permission to assign the identity at its resource scope. Release-only lookup and deletion remain available if the attachment is later removed.

To enable an identity for an existing fixed lease, stop that lease and allocate a replacement with a new lease ID. Fixed create inputs cannot change in place, and a stopped fixed lease ID remains terminal. Claims created without this setting can still be resolved and released.

Direct-mode config can be supplied entirely via environment:

AZURE_SUBSCRIPTION_ID            # or CRABBOX_AZURE_SUBSCRIPTION_ID
AZURE_TENANT_ID                  # or CRABBOX_AZURE_TENANT_ID
AZURE_CLIENT_ID                  # or CRABBOX_AZURE_CLIENT_ID
AZURE_CLIENT_SECRET              # service-principal secret (never read from config)
CRABBOX_AZURE_BACKEND            # vm | dynamic-sessions
CRABBOX_AZURE_LOCATION
CRABBOX_AZURE_RESOURCE_GROUP
CRABBOX_AZURE_USER_ASSIGNED_IDENTITY_RESOURCE_ID
CRABBOX_AZURE_IMAGE
CRABBOX_AZURE_WINDOWS_ARM64_IMAGE
CRABBOX_AZURE_OS_DISK            # managed | ephemeral | auto
CRABBOX_AZURE_VNET
CRABBOX_AZURE_SUBNET
CRABBOX_AZURE_NSG
CRABBOX_AZURE_SSH_CIDRS          # comma-separated
CRABBOX_AZURE_NETWORK            # public | private

AZURE_* are the standard service-principal variables consumed by DefaultAzureCredential. Crabbox does not read or print the client secret.

#Auth

The simplest setup uses the Azure CLI — no environment variables needed:

az login
crabbox azure login
crabbox warmup --provider azure

crabbox azure login detects the active subscription, validates credentials, and stores the subscription ID, tenant ID, and location (default eastus) in user config. After that, DefaultAzureCredential picks up the az login session automatically.

For service-principal setups (CI, automation, shared environments), use environment variables. If azure.tenantId and azure.clientId (or CRABBOX_AZURE_TENANT_ID / CRABBOX_AZURE_CLIENT_ID) are configured and AZURE_CLIENT_SECRET is set in the environment, Crabbox builds a ClientSecretCredential from those explicit values. Otherwise it falls back to azidentity.NewDefaultAzureCredential, which scans environment, workload identity, managed identity, and CLI credentials in order. The simplest service-principal grant is the Contributor role scoped to the resource group:

export AZURE_TENANT_ID=...
export AZURE_CLIENT_ID=...
export AZURE_CLIENT_SECRET=...
export AZURE_SUBSCRIPTION_ID=...

See Authenticate Go apps to Azure services with service principals.

#Brokered mode

Brokered leases reuse the same Azure service-principal secrets on the coordinator: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_SUBSCRIPTION_ID. Operators own the resource group, vnet, subnet, NSG, OS disk mode, and SSH CIDR defaults through the CRABBOX_AZURE_* env vars on the Worker. Explicit broker requests for azureImage and azureOSDisk require admin-token authentication. Normal broker users receive coordinator-managed image and OS-disk values; azureLocation remains user-selectable for capacity routing. Direct mode keeps these local overrides. Set CRABBOX_AZURE_WINDOWS_ARM64_IMAGE on the coordinator when brokered Azure Windows ARM64 leases need a default ARM64 Windows image without changing the global CRABBOX_AZURE_IMAGE fallback used by existing custom-image leases.

Set CRABBOX_AZURE_REGIONS on the coordinator for Azure-specific capacity fallback; CRABBOX_CAPACITY_REGIONS remains the AWS region fallback list.

For ordinary brokered VM creates, the coordinator checks Resource SKUs before allocating network resources. Within each region it prefers SKUs advertised as available and skips explicit subscription location restrictions. The lookup has a five-second budget and caches complete results for five minutes per client, subscription, and region. Missing entries, missing read permission, and failed lookups leave availability unknown and retain ordinary ARM fallback. These VMs are unzoned, so zone-only restrictions do not exclude a regional SKU. The catalogue describes subscription eligibility, not live capacity or proof that a VM allocation was rejected.

Ordinary brokered creates retain region preference, trying the selected market then enabled on-demand fallback in each region. With multiple regions configured, the first capacity failure in each region/market ends that SKU chain so another region is reached sooner. Single-region requests retain the full SKU chain; explicit --type requests retain that exact SKU. Confirmed capacity rejections with verified companion cleanup are cached in coordinator state for five minutes, scoped by subscription/resource group, region, SKU, and market. Later creates skip those combinations; timeouts, ambiguous allocation, and pending cleanup never populate the cache. Cached skips confer no allocation or cleanup authority. Failed attempts expose durationMs, including network setup and checked cleanup.

VM create polls default to five seconds and honor longer Retry-After delays within the remaining budget. PIP and NIC creates remain sequential because NIC references PIP and VM references NIC. VM attempt budgets remain 120 seconds for default Spot fallback and 180 seconds for on-demand; network LROs remain bounded at 60 seconds each. Ordinary brokered Azure creation has a 25-minute total API and polling budget across all candidates. Expiry stops new attempts and retains unresolved cleanup claims; it never establishes that an allocation was rejected. Native Windows durable provisioning retains its frozen candidate plan and 30-minute continuation budget. Brokered clients wait up to 31 minutes, including a response margin; an earlier caller deadline or cancellation still wins.

Run crabbox doctor --provider azure --target windows before leasing through the broker. The coordinator readiness check reports missing coordinator secret names without exposing values, and lease creation fails with provider_not_configured until the required service-principal secrets are present.

#Lifecycle

  1. Resolve credentials per the rules above.
  2. Ensure the shared resource group, virtual network, subnet, and network security group exist. Crabbox first issues Get calls against each resource:
  • If a resource exists without the managed_by=crabbox tag, Crabbox refuses to mutate it and returns an adopt-or-rename error.
  • If a resource exists with the tag, it is left alone (Crabbox does not overwrite tags, address spaces, subnets, or rules on later acquires).
  • If a resource is missing, it is created with Crabbox tags and the configured layout. Inbound SSH rules are derived from azure.sshCIDRs, the configured SSH port, and any fallback ports.
  1. Mint a per-lease SSH key.
  2. Pick the configured class SKU candidates and try each in order.
  3. Create a public IP, NIC, and VM. Linux passes cloud-init in osProfile.customData and the SSH key in osProfile.linuxConfiguration.ssh.publicKeys. Native Windows uses a Windows Server small-disk Gen2 image, Windows osProfile fields (adminPassword, computerName, windowsConfiguration), and a non-rebooting Custom Script Extension that runs the initial SSH bootstrap saved in C:\AzureData\CustomData.bin.
  4. Choose the OS disk mode (see Config above).
  5. Tag the VM, NIC, and public IP with Crabbox lease metadata.
  6. Wait for the public IP to allocate, then for SSH readiness.
  7. For --desktop on native Windows, run the shared Windows desktop bootstrap over SSH: install TightVNC, configure the generated crabbox Windows login, enable auto-logon, reboot once, and wait for SSH/VNC readiness.
  8. For --windows-mode wsl2, run the shared Windows WSL2 bootstrap over SSH: enable WSL/VirtualMachinePlatform/HypervisorPlatform, reboot as needed, import Ubuntu, and wait for the Linux-side crabbox-ready check.
  9. Let core sync and run over SSH.
  10. On direct release/cleanup, cascade-delete VM -> NIC -> public IP -> OS disk; the shared infra remains. Brokered release and orphan cleanup use the coordinator's canonical-resource, quarantine, and fresh-preflight rules; see Lifecycle and cleanup.

For ordinary direct acquisition, successful owned rollback also removes the lease's generated SSH credentials and host-trust files. Failed deletion or uncertain VM ownership retains them; a local artifact-cleanup error prevents an automatic fresh-lease retry. Fixed-ID acquisition and its durable recovery records keep their existing lifecycle.

#Brokered checkpoint ownership

New brokered Azure managed-OS-disk snapshots are coordinator-owned but manually retained unless checkpoint create --expire-unused-after <duration> or checkpoint policy explicitly opts in. Records bind the authoritative source lease, Azure subscription, resource group, location, canonical snapshot ID, and immutable snapshot identity. Provider cleanup succeeds only after exact scoped deletion is confirmed; authentication failures, wrong resource groups, and ambiguous API errors retain ownership for retry. Promoting the snapshot creates a durable pin that blocks expiry and manual checkpoint deletion until the exact promotion is replaced. Direct Azure snapshots and older image records remain operator-managed.

#Classes

Default Linux SKU candidates (first that provisions wins):

tiny      Standard_D2ads_v6, Standard_D2ds_v6, Standard_D2ads_v5, Standard_D2ds_v5, Standard_F2s_v2
small     Standard_D8ads_v6, Standard_D8ds_v6, Standard_F8s_v2, Standard_D8ads_v5, Standard_D8ds_v5, then D/F 4-vCPU fallbacks
standard  Standard_D32ads_v6, Standard_D32ds_v6, Standard_F32s_v2, Standard_D32ads_v5, Standard_D32ds_v5, then D/F 16-vCPU fallbacks
fast      Standard_D64ads_v6, Standard_D64ds_v6, Standard_F64s_v2, Standard_D64ads_v5, Standard_D64ds_v5, then D/F 48-vCPU and 32-vCPU fallbacks
large     Standard_D96ads_v6, Standard_D96ds_v6, Standard_D96ads_v5, Standard_D96ds_v5, then D/F 64-vCPU and 48-vCPU fallbacks
beast     Standard_D192ds_v6, Standard_D128ds_v6, then D/F 96-vCPU and 64-vCPU fallbacks

Default native Windows and WSL2 SKU candidates:

tiny      Standard_D2ads_v6, Standard_D2ds_v6, Standard_D2ads_v5, Standard_D2ds_v5, then Standard_D2as_v6
small     Standard_D8ads_v6, Standard_D8ds_v6, Standard_D8ads_v5, Standard_D8ds_v5, then Standard_D8as_v6
standard  Standard_D2ads_v6, Standard_D2ds_v6, Standard_D2ads_v5, Standard_D2ds_v5, then Standard_D2as_v6
fast      Standard_D4ads_v6, Standard_D4ds_v6, Standard_D4ads_v5, Standard_D4ds_v5, then Standard_D4as_v6
large     Standard_D8ads_v6, Standard_D8ds_v6, Standard_D8ads_v5, Standard_D8ds_v5, then Standard_D8as_v6
beast     Standard_D16ads_v6, Standard_D16ds_v6, Standard_D16ads_v5, Standard_D16ds_v5, then Standard_D8ads_v6

Class-based provisioning falls back across the candidate list when Azure rejects a SKU for capacity or quota (SkuNotAvailable, QuotaExceeded, AllocationFailed, OverconstrainedAllocationRequest). If the rejected attempt already created its exact lease-owned NIC and public IP, Crabbox verifies and removes both and clears their durable cleanup claim before the next SKU creates a new public IP. When capacity.regions (or broker-side CRABBOX_AZURE_REGIONS) is set, Crabbox also tries those Azure regions in order and uses region-scoped shared network names for the fallback path. Spot leases fall back to on-demand when capacity.fallback starts with on-demand. Azure Spot VMs use eviction policy Delete and billingProfile.maxPrice: -1, so price alone does not evict a lease while Azure still charges no more than the on-demand price. Explicit --type is exact for size selection but still allows configured region fallback.

#Capabilities

  • SSH: yes.
  • Crabbox sync: yes.
  • Native Windows: SSH, sync, run, and desktop/VNC.
  • Windows WSL2: SSH, sync, run, and Actions hydration (through the POSIX WSL contract).
  • Desktop: Linux and native Windows.
  • Browser / code: Linux only.
  • Tailscale: Linux managed leases.
  • Cleanup: yes.
  • Coordinator (brokered): Linux, native Windows, and WSL2 leases.

Azure does not provision macOS through this provider. Use AWS or provider: ssh for macOS targets.

#Gotchas

  • Azure VM names are constrained to 1-64 characters and cannot contain underscores. The leaseProviderName helper substitutes dashes for underscores; keep that constraint in mind if you customize naming.
  • Windows computer names are limited to 15 characters. Crabbox keeps the VM resource name stable and derives a shorter Windows computerName.
  • The first acquire in an empty subscription pays the cost of creating the shared resource group, vnet, and NSG. Later acquires only create per-lease resources.
  • Shared Azure network resources are regional. Leases use the configured azure.vnet/azure.nsg names when they match the target region. If a Crabbox-managed VNet or NSG with that base name already exists in another region, Crabbox automatically uses region-scoped names such as crabbox-vnet-westeurope. Multi-region fallback also uses region-scoped shared network names, so one managed resource group can hold fallback networks safely.
  • If you already have a resource group / vnet / NSG with the configured names, Crabbox refuses to mutate them unless they carry the managed_by=crabbox tag. Tag them to adopt, choose different names in azure.* config, or let Crabbox create dedicated resources.
  • Direct Azure list, stop, and cleanup only treat a VM as Crabbox-owned when its tags include crabbox=true, created_by=crabbox, provider=azure, a canonical cbx_ lease ID, and a non-empty slug. Stop additionally requires the resolved lease ID to match the VM's lease tag. Legacy or manually tagged VMs are skipped; retagging only with crabbox=true never authorizes deletion.
  • When azure.sshCIDRs is empty on the public network path, direct Azure provisioning detects the operator's outbound IPv4 and creates a /32 SSH rule. If a shared NSG already has a different managed SSH CIDR, set CRABBOX_AZURE_SSH_CIDRS explicitly to replace or extend it. Private-network leases also require explicit VPN/VNet source CIDRs.
  • Azure costs are not hardcoded in Crabbox. Set CRABBOX_COST_RATES_JSON when you need exact Azure cost guardrails.
  • Azure native Windows uses a Custom Script Extension because Windows custom data is written to disk but not executed by Azure provisioning. Keep that extension path non-rebooting; Windows desktop/VNC setup runs later over SSH.
  • Direct-mode cleanup is best effort. Use crabbox cleanup --provider azure to sweep expired direct leases.