Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## 0.22.1 - Unreleased
## 0.22.1 - 2026-05-29

### Added

- Added `--arch arm64` / `architecture: arm64` for Linux ARM leases on Azure and AWS, including Azure Dpsv6/Dpdsv6 and AWS Graviton class fallback plus matching Ubuntu ARM64 image resolution.

### Fixed

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ AWS Linux standard c7a/c7i/m7a/m7i.8xlarge family
fast …16xlarge family
large …24xlarge family
beast …48xlarge family, falling back to 32x/24x/16x
arm64 c7g/m7g/r7g families with --arch arm64

AWS Win standard m7i.large, m7a.large, t3.large
fast m7i.xlarge, m7a.xlarge, t3.xlarge
Expand All @@ -255,6 +256,7 @@ Azure standard Standard_D32ads_v6, Standard_D32ds_v6, Standard_F32s_v2, th
fast Standard_D64ads_v6, Standard_D64ds_v6, Standard_F64s_v2, then 48/32-vCPU fallbacks
large Standard_D96ads_v6, Standard_D96ds_v6, then 64/48-vCPU fallbacks
beast Standard_D192ds_v6, Standard_D128ds_v6, then 96/64-vCPU fallbacks
arm64 Standard_D*ps_v6 / D*pds_v6 Cobalt families with --arch arm64

Azure Win/
WSL2 standard Standard_D2ads_v6, Standard_D2ds_v6, Standard_D2ads_v5, Standard_D2ds_v5, Standard_D2as_v6
Expand All @@ -273,7 +275,9 @@ Cloudflare standard standard-4
beast standard-4
```

Override with `--type` or `CRABBOX_SERVER_TYPE` for a specific instance.
Override with `--type` or `CRABBOX_SERVER_TYPE` for a specific instance. Use
`--arch arm64` / `architecture: arm64` for Linux ARM capacity on Azure or AWS;
explicit ARM provider types also select ARM images when no custom image is set.
Cloudflare also accepts `lite`, `basic`, `standard-1`, `standard-2`, and
`standard-3` as smaller explicit `--type` values; `standard-4` is the default.
Providers without a row either use provider-native capacity settings or reject
Expand Down
1 change: 1 addition & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ CRABBOX_ORG owning org
CRABBOX_PROFILE default profile
CRABBOX_CONFIG path to an explicit config file
CRABBOX_DEFAULT_CLASS default machine class
CRABBOX_ARCH default CPU architecture (amd64|arm64)
CRABBOX_SERVER_TYPE provider server/instance type override
CRABBOX_IDLE_TIMEOUT idle expiry
CRABBOX_TTL max lease lifetime
Expand Down
3 changes: 2 additions & 1 deletion docs/commands/job.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
test-wsl2:
provider: aws
target: windows
architecture: amd64
windows:
mode: wsl2
class: beast
Expand All @@ -110,7 +111,7 @@ jobs:

Routing and lease creation:

- `provider`, `target` (or `targetOS`), `windows.mode`, `profile`, `class`.
- `provider`, `target` (or `targetOS`), `windows.mode`, `profile`, `class`, `architecture`.
- `type` (or `serverType`), `market` (or `capacity.market`).
- `ttl`, `idleTimeout`, `desktop`, `desktopEnv`, `browser`, `code`, `network`.

Expand Down
2 changes: 2 additions & 0 deletions docs/commands/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ crabbox run --id swift-crab -- pnpm test:changed
crabbox run --class beast -- pnpm check
crabbox run --provider aws --class beast --market on-demand -- pnpm check
crabbox run --provider azure --class beast -- pnpm check
crabbox run --provider azure --arch arm64 --class fast -- go test ./...
crabbox run --tailscale -- pnpm check
crabbox run --id swift-crab --network tailscale -- pnpm test
crabbox run --browser -- google-chrome --headless --version
Expand Down Expand Up @@ -381,6 +382,7 @@ lease-acting commands):
--provider <name> See `crabbox providers` for the full list.
--profile <name>
--class <name>
--arch amd64|arm64 CPU architecture; arm64 is Linux-only on AWS/Azure.
--os <selector> Portable Linux OS image, e.g. ubuntu:26.04
--type <provider-type>
--market spot|on-demand
Expand Down
2 changes: 2 additions & 0 deletions docs/commands/warmup.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ crabbox warmup --class beast
crabbox warmup --provider aws --class beast --market on-demand
crabbox warmup --provider aws --os ubuntu:26.04 --desktop --browser --desktop-env wayland
crabbox warmup --provider azure --class beast
crabbox warmup --provider azure --arch arm64 --class fast
crabbox warmup --browser
crabbox warmup --tailscale
crabbox warmup --slug update-flow-smoke
Expand Down Expand Up @@ -189,6 +190,7 @@ warmup, because it also dispatches the workflow and waits for the ready marker.
--provider <name> provider (see crabbox providers); default hetzner
--profile <name> configuration profile
--class <name> machine class; default beast
--arch amd64|arm64 CPU architecture; arm64 is Linux-only on AWS/Azure
--os ubuntu:26.04|ubuntu:24.04 portable Linux OS image selector
--type <provider-type> provider server/instance type
--market spot|on-demand capacity market (AWS)
Expand Down
14 changes: 11 additions & 3 deletions docs/features/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Mac. AWS is one of the four brokerable providers, so it can run two ways:

```sh
crabbox warmup --provider aws --class beast
crabbox warmup --provider aws --arch arm64 --class fast
crabbox run --provider aws --class beast --market on-demand -- pnpm check
crabbox warmup --provider aws --target windows --desktop
crabbox warmup --provider aws --target windows --windows-mode wsl2
Expand Down Expand Up @@ -83,6 +84,12 @@ fast c7a.16xlarge, c7i.16xlarge, m7a.16xlarge, m7i.16xlarge, c7a.12xlarge,
large c7a.24xlarge, c7i.24xlarge, m7a.24xlarge, m7i.24xlarge, r7a.24xlarge, c7a.16xlarge, c7a.12xlarge
beast c7a.48xlarge, c7i.48xlarge, m7a.48xlarge, m7i.48xlarge, r7a.48xlarge, c7a.32xlarge, c7i.32xlarge, m7a.32xlarge, c7a.24xlarge, c7a.16xlarge

AWS Linux ARM64 (--arch arm64)
standard c7g.8xlarge, m7g.8xlarge, r7g.8xlarge, c7g.4xlarge
fast c7g.16xlarge, m7g.16xlarge, r7g.16xlarge, c7g.12xlarge, c7g.8xlarge
large c7g.16xlarge, m7g.16xlarge, r7g.16xlarge, c7g.12xlarge
beast c7g.16xlarge, m7g.16xlarge, r7g.16xlarge, c7g.12xlarge

AWS Windows
standard m7i.large, m7a.large, t3.large
fast m7i.xlarge, m7a.xlarge, t3.xlarge
Expand All @@ -106,9 +113,10 @@ from the C8i/M8i/M8i-flex/R8i families; Crabbox rejects unsupported families

## Images

- **Linux** resolves the latest Ubuntu 26.04 x86_64 AMI from Canonical. Pass
`--os ubuntu:24.04` for the previous LTS. Supported selectors: `ubuntu:26.04`
and `ubuntu:24.04`.
- **Linux** resolves the latest Ubuntu 26.04 AMI from Canonical for the selected
architecture. Pass `--arch arm64` for Graviton/ARM64 capacity and
`--os ubuntu:24.04` for the previous LTS. Supported selectors:
`ubuntu:26.04` and `ubuntu:24.04`.
- **Windows** resolves the latest Windows Server 2022 English Full Base AMI.
- **macOS** resolves the matching Amazon EC2 macOS AMI for the chosen instance
family (arm64 for Apple silicon, x86_64 for `mac1.metal`).
Expand Down
14 changes: 14 additions & 0 deletions docs/features/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dynamic-sessions`; see the [providers overview](providers.md).

```sh
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 warmup --provider azure --target windows --class standard
Expand All @@ -58,6 +59,16 @@ large Standard_D96ads_v6, Standard_D96ds_v6, then D/F 64- and 48-vCPU fallba
beast Standard_D192ds_v6, Standard_D128ds_v6, then D/F 96- and 64-vCPU fallbacks
```

Linux ARM64 classes use Azure Cobalt Dpsv6/Dpdsv6 candidates and ARM64 Ubuntu
Marketplace images:

```text
standard Standard_D32pds_v6, Standard_D32ps_v6, then 16-vCPU fallbacks
fast Standard_D64pds_v6, Standard_D64ps_v6, then 48/32-vCPU fallbacks
large Standard_D96pds_v6, Standard_D96ps_v6, then 64/48-vCPU fallbacks
beast Standard_D96pds_v6, Standard_D96ps_v6, then 64-vCPU fallbacks
```

Native Windows and WSL2 use a smaller scale, and the default candidates support
the nested virtualization WSL2 needs:

Expand Down Expand Up @@ -174,6 +185,9 @@ existing vnet and NSG, or pick distinct `azure.vnet`, `azure.subnet`, and
The default location is `eastus`. The default Linux image is
`Canonical:ubuntu-26_04-lts:server:latest`; native Windows defaults to
`MicrosoftWindowsServer:windowsserver2022:2022-datacenter-smalldisk-g2:latest`.
With `architecture: arm64` or `--arch arm64`, Linux defaults switch to
`Canonical:ubuntu-26_04-lts:server-arm64:latest` or the matching
`ubuntu-24_04-lts:server-arm64` image when `--os ubuntu:24.04` is set.
Set `azure.image` / `CRABBOX_AZURE_IMAGE` as a `Publisher:Offer:SKU:Version`
reference to override.

Expand Down
9 changes: 8 additions & 1 deletion docs/features/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ broker:

provider: aws # default provider when --provider is unset
target: linux # default target OS: linux | macos | windows
architecture: amd64 # amd64 | arm64; arm64 is Linux-only on AWS/Azure
os: ubuntu:26.04 # OS image; resolved to per-provider images for linux
windows:
mode: normal # normal | wsl2 when target=windows
Expand Down Expand Up @@ -176,6 +177,7 @@ jobs:
windows-wsl2:
provider: aws
target: windows
architecture: amd64
windows:
mode: wsl2
class: beast
Expand Down Expand Up @@ -679,6 +681,10 @@ Class-to-type mappings live in [Providers](providers.md). When you set
ignored. The `serverType:` and `--type` paths intentionally do not fall back;
they fail loud if the provider rejects the type.

Set `architecture: arm64` (or `--arch arm64`) for Linux ARM capacity on AWS or
Azure. Explicit ARM provider types also select matching ARM Linux images when
no provider-specific image override is set.

## Environment variables

Most YAML keys have a matching `CRABBOX_*` env override that takes precedence
Expand All @@ -694,6 +700,7 @@ CRABBOX_ACCESS_CLIENT_ID Cloudflare Access service-token id
CRABBOX_ACCESS_CLIENT_SECRET Cloudflare Access service-token secret
CRABBOX_PROVIDER default provider
CRABBOX_TARGET default target OS
CRABBOX_ARCH default architecture: amd64 or arm64
CRABBOX_OS default OS image
CRABBOX_PROFILE default profile
CRABBOX_DEFAULT_CLASS default machine class
Expand Down Expand Up @@ -726,7 +733,7 @@ MODAL_TOKEN_ID / MODAL_TOKEN_SECRET
| Setting | User config | Repo config | Profile | Notes |
|:--------|:------------|:------------|:--------|:------|
| `broker.url` and `broker.token` | yes | no | no | Per-machine identity. |
| `provider`, `class`, `serverType` | optional default | yes | yes | Per-repo defaults; profiles for lanes. |
| `provider`, `class`, `architecture`, `serverType` | optional default | yes | yes | Per-repo defaults; profiles for lanes. |
| `sync.exclude`, `sync.fingerprint`, `sync.baseRef` | no | yes | yes | Lives with the repo. |
| `env.allow` | no | yes | yes | Repo decides what is safe to forward. |
| Per-user SSH key path | yes | no | no | Personal preference. |
Expand Down
3 changes: 2 additions & 1 deletion docs/features/jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ project-specific parallelism.

Belongs in a Crabbox job:

- provider, target OS, Windows mode, profile, class/type, market, network;
- provider, target OS, architecture, Windows mode, profile, class/type, market, network;
- lease TTL, idle timeout, and stop policy;
- whether to run Actions hydration and how long to wait for it;
- the remote command and whether it runs through a shell;
Expand Down Expand Up @@ -142,6 +142,7 @@ windows:
mode: wsl2 # normal | wsl2; sets --windows-mode
profile: project-check
class: beast
architecture: amd64 # amd64 | arm64; arm64 is Linux-only on AWS/Azure
type: m8i.4xlarge # alias: serverType
market: on-demand # alias: capacity.market
network: auto
Expand Down
7 changes: 7 additions & 0 deletions docs/providers/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Prefer [Hetzner](./hetzner.md) for cheaper Linux-only capacity, or

```sh
crabbox warmup --provider aws --class standard
crabbox warmup --provider aws --arch arm64 --class fast
crabbox run --provider aws --class fast -- pnpm test
crabbox run --provider aws --market on-demand -- pnpm check
crabbox warmup --provider aws --target windows --desktop
Expand Down Expand Up @@ -63,6 +64,7 @@ class is `beast`.
```yaml
provider: aws
target: linux
architecture: amd64
class: beast
market: spot
aws:
Expand All @@ -76,6 +78,11 @@ aws:
macHostId: "" # pin an EC2 Mac Dedicated Host
```

Set `architecture: arm64` or pass `--arch arm64` for Linux Graviton leases.
Crabbox switches class fallback to C7g/M7g/R7g families and resolves Canonical
Ubuntu ARM64 AMIs unless `aws.ami` is pinned. ARM64 is not supported for managed
Windows or WSL2 targets.

### Environment variables (direct mode)

```text
Expand Down
7 changes: 7 additions & 0 deletions docs/providers/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Azure supports both execution modes:

```sh
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
Expand Down Expand Up @@ -67,6 +68,7 @@ Azure-family backend:
```yaml
provider: azure
target: linux
architecture: amd64
class: beast
azure:
backend: vm
Expand All @@ -88,6 +90,11 @@ azure:
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.
ARM64 is not supported for native Windows, WSL2, or macOS targets.

`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.
Expand Down
24 changes: 18 additions & 6 deletions internal/cli/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (c *AWSClient) SpotPlacementScores(ctx context.Context, cfg Config) ([]type
if len(regions) == 0 {
return nil, nil
}
candidates := awsInstanceTypeCandidatesForClass(cfg.Class)
candidates := awsInstanceTypeCandidatesForConfig(cfg)
if cfg.ServerType != "" {
candidates = appendUniqueStrings([]string{cfg.ServerType}, candidates...)
}
Expand Down Expand Up @@ -650,14 +650,15 @@ func (c *AWSClient) resolveAMI(ctx context.Context, cfg Config) (string, error)
name, architecture := awsMacOSAMIQueryForInstanceType(cfg.ServerType)
return c.resolveLatestAmazonAMI(ctx, name, architecture)
}
name, label, err := awsLinuxAMIQueryForOS(cfg.OSImage)
architecture := awsLinuxImageArchitecture(effectiveArchitectureForConfig(cfg))
name, label, err := awsLinuxAMIQueryForOS(cfg.OSImage, effectiveArchitectureForConfig(cfg))
if err != nil {
return "", err
}
out, err := c.ec2.DescribeImages(ctx, &ec2.DescribeImagesInput{
Owners: []string{awsUbuntuOwner},
Filters: []types.Filter{
{Name: aws.String("architecture"), Values: []string{"x86_64"}},
{Name: aws.String("architecture"), Values: []string{architecture}},
{Name: aws.String("name"), Values: []string{name}},
{Name: aws.String("root-device-type"), Values: []string{"ebs"}},
{Name: aws.String("virtualization-type"), Values: []string{"hvm"}},
Expand All @@ -667,14 +668,21 @@ func (c *AWSClient) resolveAMI(ctx context.Context, cfg Config) (string, error)
return "", err
}
if len(out.Images) == 0 {
return "", exit(3, "no %s x86_64 AMI found in %s; set CRABBOX_AWS_AMI", label, cfg.AWSRegion)
return "", exit(3, "no %s %s AMI found in %s; set CRABBOX_AWS_AMI", label, architecture, cfg.AWSRegion)
}
sort.Slice(out.Images, func(i, j int) bool {
return aws.ToString(out.Images[i].CreationDate) > aws.ToString(out.Images[j].CreationDate)
})
return aws.ToString(out.Images[0].ImageId), nil
}

func awsLinuxImageArchitecture(architecture string) string {
if architecture == ArchitectureARM64 {
return "arm64"
}
return "x86_64"
}

func awsMacOSAMIQueryForInstanceType(instanceType string) (string, string) {
if strings.HasPrefix(instanceType, "mac1.") {
return "amzn-ec2-macos-14.*", "x86_64_mac"
Expand Down Expand Up @@ -1024,6 +1032,9 @@ func awsLaunchCandidates(cfg Config) []string {
return appendUniqueStrings([]string{cfg.ServerType}, awsInstanceTypeCandidatesForConfig(cfg)...)
}
fallback := "t3.small"
if cfg.TargetOS == targetLinux && effectiveArchitectureForConfig(cfg) == ArchitectureARM64 {
fallback = "t4g.small"
}
if cfg.TargetOS == targetWindows {
fallback = "t3.large"
if cfg.WindowsMode == windowsModeWSL2 {
Expand Down Expand Up @@ -1147,17 +1158,18 @@ func awsRecommendedClassForQuota(cfg Config, limitVCPUs int) (string, string) {
if limitVCPUs <= 0 {
return "", ""
}
architecture := effectiveArchitectureForConfig(cfg)
classes := []string{"beast", "large", "fast", "standard"}
for _, class := range classes {
candidates := awsInstanceTypeCandidatesForTargetModeClass(cfg.TargetOS, cfg.WindowsMode, class)
candidates := awsInstanceTypeCandidatesForTargetModeArchitectureClass(cfg.TargetOS, cfg.WindowsMode, architecture, class)
if len(candidates) == 0 {
continue
}
if awsInstanceTypeVCPUs(candidates[0]) <= limitVCPUs {
return class, candidates[0]
}
}
for _, serverType := range awsInstanceTypeCandidatesForTargetModeClass(cfg.TargetOS, cfg.WindowsMode, "standard") {
for _, serverType := range awsInstanceTypeCandidatesForTargetModeArchitectureClass(cfg.TargetOS, cfg.WindowsMode, architecture, "standard") {
if awsInstanceTypeVCPUs(serverType) <= limitVCPUs {
return "standard", serverType
}
Expand Down
19 changes: 19 additions & 0 deletions internal/cli/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ func TestAWSCapacityDoctorCheckWarnsWhenQuotaBelowDefaultClass(t *testing.T) {
}
}

func TestAWSCapacityDoctorCheckRecommendsARM64Types(t *testing.T) {
cfg := defaultConfig()
cfg.Provider = "aws"
cfg.TargetOS = targetLinux
cfg.Class = "beast"
cfg.Architecture = ArchitectureARM64
cfg.architectureExplicit = true
cfg.ServerType = serverTypeForConfig(cfg)

check := awsCapacityDoctorCheckForQuota(cfg, "spot", 32, true, nil)

if check.Status != "warning" {
t.Fatalf("status=%q, want warning", check.Status)
}
if check.Details["recommended_class"] != "standard" || check.Details["recommended_type"] != "c7g.8xlarge" {
t.Fatalf("recommendation=(%q,%q), want standard/c7g.8xlarge", check.Details["recommended_class"], check.Details["recommended_type"])
}
}

func TestAWSCapacityDoctorCheckPassesWhenQuotaCoversDefaultClass(t *testing.T) {
cfg := defaultConfig()
cfg.Provider = "aws"
Expand Down
Loading