AWS Spot Fleet Guide
Sign-in methods, least-privilege IAM policy, regions, Spot vCPU quotas, VPC networking, cost guardrails, and the 6-stage life of a Spot VM.
spotexec drives EC2 Spot fleets directly from your workstation using hand-signed AWS SigV4 requests—there is no cloud control plane, no CloudFormation stack to maintain, and no IAM role attached to runner VMs.
Fast path: Sign in once with an administrative AWS session and run spotexec setup. It creates a dedicated IAM user (spotexec-<hostname>), attaches the least-privilege policy below, writes a non-expiring profile (spotexec), ensures the Spot service-linked role and default VPC exist, and verifies your regional Spot vCPU quota.
This guide documents every underlying AWS detail if you prefer to configure IAM, networking, or multi-size fleets manually.
1. AWS Sign-In Methods#
spotexec resolves credentials in the following order:
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN(only whenprofileis unset on the runner).aws configure export-credentials --profile <profile>when AWS CLI v2 is installed—supporting IAM Identity Center (SSO),aws login(CLI 2.32+), static IAM keys,assume-role, andcredential_process. If an SSO session has expired,spotexecreportsrun aws sso loginclearly instead of silently falling back to another credential source.- Without the AWS CLI installed,
spotexecreads~/.awsdirectly for static keys,credential_process, cached IAM Identity Center tokens (including token refresh), and activeaws loginsessions.
2. Least-Privilege IAM Policy#
Run spotexec setup --print-policy > spotexec-policy.json at any time to export the exact policy. Statements 1–3 are required; statements 4–8 enable the S3 dependency cache, ECR project containers, spotexec doctor quota checks, and spotexec image bake:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": [
"ec2:RunInstances", "ec2:CreateTags", "ec2:DescribeInstances", "ec2:TerminateInstances",
"ec2:DescribeImages", "ec2:DescribeVpcs", "ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups", "ec2:CreateSecurityGroup", "ec2:AuthorizeSecurityGroupIngress"
], "Resource": "*" },
{ "Effect": "Allow", "Action": "ssm:GetParameter",
"Resource": "arn:aws:ssm:*::parameter/aws/service/canonical/*" },
{ "Effect": "Allow", "Action": "iam:CreateServiceLinkedRole", "Resource": "*",
"Condition": { "StringEquals": { "iam:AWSServiceName": "spot.amazonaws.com" } } },
{ "Effect": "Allow", "Action": [
"s3:CreateBucket", "s3:PutLifecycleConfiguration", "s3:ListBucket",
"s3:GetObject", "s3:PutObject"
], "Resource": ["arn:aws:s3:::spotexec-cache-*", "arn:aws:s3:::spotexec-cache-*/*"] },
{ "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" },
{ "Effect": "Allow", "Action": [
"ecr:DescribeRepositories", "ecr:CreateRepository", "ecr:TagResource", "ecr:PutLifecyclePolicy",
"ecr:BatchCheckLayerAvailability", "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage"
], "Resource": "arn:aws:ecr:*:*:repository/spotexec/*" },
{ "Effect": "Allow", "Action": "servicequotas:GetServiceQuota", "Resource": "*" },
{ "Effect": "Allow", "Action": [
"ec2:StopInstances", "ec2:CreateImage", "ec2:DeregisterImage", "ec2:DeleteSnapshot"
], "Resource": "*" }
]
}If your AWS account has never launched a Spot instance before and your identity cannot create service-linked roles, ask an administrator to run once:
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com3. Region Selection & Spot vCPU Quotas#
Always set region explicitly on each [runners.<name>] block (~/.aws/config profile regions are intentionally ignored so runner behavior is deterministic across machines). Pick the region physically closest to you to keep SSH ControlMaster round-trips sub-second, and list 2–3 instance_types of the same CPU architecture (c7a.xlarge, c6a.xlarge, m7a.xlarge) so capacity shortages in one pool fall over immediately.
Checking Spot vCPU Quotas (L-34B43A08)#
New AWS accounts often start with a very low All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests quota (sometimes 5 vCPUs). Ensure your quota covers max_instances × vCPUs across your fleets:
# Inspect current Spot vCPU quota
aws service-quotas get-service-quota --profile spotexec --region us-east-2 \
--service-code ec2 --quota-code L-34B43A08 --query Quota.Value
# Request an increase (requires an admin session)
aws service-quotas request-service-quota-increase --region us-east-2 \
--service-code ec2 --quota-code L-34B43A08 --desired-value 644. Networking & Security Groups#
By default, spotexec launches instances into the region's default VPC, assigns a public IPv4 address, and manages a spotexec-ssh security group allowing inbound TCP 22 from ssh_cidr (0.0.0.0/0 by default, using Ed25519 key-only authentication generated at ~/.config/spotexec/keys/id_ed25519).
- Locking down SSH ingress: Set
ssh_cidr = "203.0.113.4/32"if you have a static office/home IP. - Custom VPCs: Set
subnet_idto a public subnet (routing0.0.0.0/0to an Internet Gateway) and optionallysecurity_group_idsallowing inbound TCP 22 from your workstation and outbound HTTPS for package registries, S3, and ECR.
5. What Gets Created in Your Account#
| AWS Resource | Created When | Lifecycle & Cleanup |
|---|---|---|
IAM user spotexec-<hostname> (/spotexec/) | spotexec setup | Delete user in IAM to revoke that machine. |
Spot EC2 instances (spotexec-runner, spotexec-owner tags) | On demand via sx or spotexec up | Self-terminate on idle (idle_timeout) or max_lifetime; spotexec down terminates immediately. |
Security group spotexec-ssh | First launch without security_group_ids | Reusable across runs; delete manually if uninstalling. |
S3 bucket spotexec-cache-<account>-<region> | First run with [deps] | Private bucket; objects auto-expire after 14 days. |
ECR repository spotexec/<repo-id> | First [container] Dockerfile build | Lifecycle policy retains only the 10 newest images. |
| Baked AMIs & EBS snapshots | spotexec image bake | Keeps 2 newest per runner; spotexec image clear deletes all. |
6. The 6-Stage Life of a Spot VM#
- Launch.
sxsubmits a one-time SpotRunInstancescall capped atmax_price(default: on-demand price) with IMDSv2 required andInstanceInitiatedShutdownBehavior=terminate. Capacity errors immediately step throughinstance_types. - Arm Watchdog First. The very first lines of
cloud-inituser-data (spotexec cloud-init <runner>) install and start thesystemdidle timer andmax_lifetimehard timer—before SSH keys or bootstrap scripts run. Even a hung or broken bootstrap cannot leak an immortal VM. - Bootstrap.
bootstrap_script(asroot) anduser_bootstrap_script(asubuntu) run while holding the lifecycleflock. If either exits non-zero, the VM marks itself failed, returnsBOOTSTRAP_FAILEDwith the tail of/var/log/spotexec-bootstrap.log, and pauses fleet launches for 1 hour so a broken script cannot burn money in a retry loop. - Serve. Concurrent
sxsessions multiplex over SSHControlMaster, acquire slot locks, and refresh the activity timestamp. - Spot Interruption Handling.
spotexec-helperpolls IMDSv2 every 5s. On a 2-minutespot/instance-actionnotice, it marks the VMdraining(finishing in-flight jobs while refusing new ones) and notifiessx, which immediately acquires or launches replacement capacity in the background. Any job cut off before completion is replayed once automatically (--no-retrydisables replay for non-idempotent deploy scripts). Advisoryrebalancenotices are logged without draining to avoid unnecessary fleet churn. - Self-Termination. Once idle for
idle_timeout(default10m), or immediately once adrainingVM finishes its active slots, or atmax_lifetime(12h), the VM powers itself off—which EC2 immediately transitions toterminated, stopping EBS and compute billing.