Skip to content

AWS (bare metal)

AWS is *.metal only. Standard EC2 with Nitro nested virt does not surface /dev/kvm to Firecracker — only true bare-metal SKUs do.

Verified end-to-end 2026-05-06 on c5.metal in us-east-1. Cycle ≈ 35 min wall, ≈ $2.50 in AWS charges.

  • INSTANCE_TYPE=c5.metal (~$4.08/hr; rejected if it doesn’t match *.metal)
  • REGION=us-east-1
  • BOOT_DISK_SIZE=50 (Ubuntu AMI default 8GB runs out mid-build)
  • Image: latest Canonical Ubuntu 24.04 LTS amd64 (resolved by AMI filter at call time so it doesn’t drift)
  • aws CLI v2 authenticated: aws configure (access key + secret + default region)
  • A default VPC with subnets in the chosen region. If not, set AWS_SUBNET_ID=<subnet-id>.

The adapter handles SG creation lazily — see “Bugs caught” below.

Terminal window
PROVIDER=aws bash ops/ephemeral/up.sh
# ... 15–25 min (bare metal allocation is slow) ...
PROVIDER=aws LABEL=<label> bash ops/ephemeral/down.sh

Bare-metal SKUs that work: c5.metal, c5n.metal, c5d.metal, c6i.metal, c7i.metal, m5.metal, m6i.metal, m7i.metal, i3.metal, etc.

  1. Default-VPC default-SG blocks inbound SSH. Only intra-SG ingress is allowed, so ssh root@<ip> from the operator workstation never reaches the box. Adapter now lazily creates a per-region SG named mvm-ephemeral-ssh with a 22/tcp ingress rule for the operator’s current public IP (looked up via https://checkip.amazonaws.com). AWS_SG_ID overrides for an externally managed group.
  2. 8GB Ubuntu AMI root volume runs out mid-build. After cloud-init’s apt
    • rustup + Nix + cargo-tools install, ~5GB is used and ~1.7GB remains free, far short of mvmd’s target/. Adapter now sets --block-device-mappings to a 50GB gp3 root.
  3. Ubuntu AMIs ship /root/.ssh/authorized_keys with a forced “login as ubuntu” command. ssh root@<ip> connects but the forced command prints the message and disconnects. Adapter splices a clean write_files entry into cloud-init’s existing write_files: list (two top-level write_files keys would last-wins, not merge).

local sg_id=$(aws ec2 create-security-group ... --query 'GroupId') silently masks the AWS API’s non-zero exit code — local’s own return is 0, so set -euo pipefail doesn’t catch a failed RHS. When the SG-create call failed (we’d put an em-dash in the description; AWS rejects non-ASCII), $sg_id was quietly empty and the next four AWS calls all errored out on malformed input.

Fix: explicit [ -z "$var" ] && return 1 after every API call that produces an id we depend on.