Custom AMIs for BYOC Runners
Build your own AMI for WarpBuild BYOC runners on AWS. Linux and Windows image requirements, a packer template, the add-image step, and the rebuild cost model.
A custom AMI lets a WarpBuild BYOC runner boot with your compilers, SDKs, and internal certificates already installed, so a GitHub Actions job starts working instead of installing. On AWS the path is: build the AMI in your own account, register it on the custom images page, attach it to a custom runner set, then point runs-on at the resulting warp-custom- label.
This page covers the documented image requirements for Linux and Windows AMIs, a packer template and the workflow that builds it, the registration step, and the maintenance model with the rebuild cadence priced against per-job install time.
Overview
BYOC runs on AWS, GCP, and Azure, and each cloud takes its own image format. On AWS the unit is an AMI, and the custom VM images guide supports Linux and Windows based AMIs. The custom AMI path applies to the Linux and Windows runner sets you declare inside your own AWS account, and starts from a connected cloud account and stack as described on BYOC runners on AWS.
Three properties are worth knowing before you build anything.
Images are scoped to the stack region. The add-image dialog lists the images in the region your stack was created in. A stack region cannot be changed after creation, so an AMI built in us-east-1 is only selectable by a stack in us-east-1. Multi-region fleets copy the AMI per region and register each copy.
A custom image carries no image fee. The custom VM images guide states there is no additional cost for using custom VM images. A BYOC runner set on your own AMI bills $0.002 per runner minute in WarpBuild fees for Linux and $0.002 per runner minute for Windows, the same as a stock image, with EC2 and EBS on your own AWS bill (WarpBuild pricing, checked 2026-08-13).
The base image is yours to keep valid. WarpBuild launches the instance and registers a just-in-time runner against your GitHub organization; everything above the agent is what you baked in. The term itself is defined on runner image, and custom runner images for GitHub Actions covers the container image path for hosted runners.
Architecture
The WarpBuild agent is what turns your AMI into a GitHub Actions runner, and its requirements are the whole contract.
| Requirement | Linux | Windows | Why it exists |
|---|---|---|---|
| Init system | systemd required, so Ubuntu and Amazon Linux 2023 based AMIs work | Not applicable | The WarpBuild agent runs as a systemd unit |
| Must be installed | curl, wget, bash, jq, libicu | aria2, reachable through the system PATH | libicu is a .NET runtime dependency of the GitHub Actions runner; aria2 replaces the slow default Windows download method |
| Must not be removed | tar, gzip, coreutils (id, chpasswd, chown, tr, sed), shadow-utils (useradd, usermod), systemd | Default system tooling | Present on supported distros by default and used during runner setup |
| Generalization | Not required | EC2 instance must be sysprepped before the image is created | An unsysprepped Windows AMI produces instances that share machine identity |
| Job user | runner user with passwordless sudo | Jobs run as runneradmin, the same user GitHub's Windows runners use | Added if absent, so user-scoped environment variables set under another account are not visible |
Two Windows details catch teams during the first boot test. User-scoped environment variables set while you were building the image belong to the account you were logged in as, and jobs run as runneradmin, so move them to machine level. RDP port 3389 is not opened by a new stack, so debugging a Windows AMI interactively means adding an inbound rule for your own CIDR block first. The Windows BYOC runners on AWS page covers the instance sizing and disk throughput those runners want.
Configuration
Build the AMI with packer against a supported base, install the required packages, and create the runner user and the tool cache directories the GitHub Actions runner expects.
variable "aws_region" {
type = string
default = "us-east-1"
}
locals {
version = "1.0.0"
}
source "amazon-ebs" "my-custom-ci" {
region = var.aws_region
instance_type = "t3.micro"
ami_name = "my-custom-ci-v${local.version}"
tags = {
Name = "my-custom-ci-v${local.version}"
team = "platform"
build_date = "{{timestamp}}"
version = local.version
provider = "packer"
product = "github-actions"
}
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.*-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["099720109477"]
most_recent = true
}
ssh_username = "ubuntu"
launch_block_device_mappings {
device_name = "/dev/sda1"
volume_type = "gp3"
volume_size = 8
delete_on_termination = true
}
}
build {
sources = ["source.amazon-ebs.my-custom-ci"]
provisioner "shell" {
inline = [
"sudo groupadd runner || echo 'Group runner already exists'",
"sudo useradd -m -g runner -s /bin/bash runner || echo 'User runner already exists'",
"echo 'runner ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/runner",
"sudo chmod 440 /etc/sudoers.d/runner",
"sudo apt-get update",
"sudo apt-get install -y curl wget unzip git jq libicu-dev",
"sudo mkdir -p /opt/hostedtoolcache/Python /opt/hostedtoolcache/Node /opt/hostedtoolcache/go",
"sudo chown -R runner:runner /opt/hostedtoolcache",
"sudo mkdir -p /home/runner/_work /home/runner/_tool /home/runner/_temp",
"sudo chown -R runner:runner /home/runner/_work /home/runner/_tool /home/runner/_temp",
]
}
}Build it from GitHub Actions so the image has the same review path as the rest of the repository.
name: build-runner-ami
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "packer/github-actions-ami/**"
permissions:
contents: read
id-token: write
jobs:
build-ami:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/PackerBuildRole
aws-region: us-east-1
- uses: hashicorp/setup-packer@v3
- run: packer plugins install github.com/hashicorp/amazon
- run: packer validate packer/github-actions-ami/my-custom-ci.pkr.hcl
- run: packer build packer/github-actions-ami/my-custom-ci.pkr.hclRegistration is four steps, and only the first is a one-time setup.
- Create a WarpBuild stack in the region the AMI lives in.
- Open the custom images page, press Add Image, and pick the AMI from the list of images in that region.
- Create a custom runner set that uses the image, along with the instance type priority list and the disk configuration.
- Reference the runner set in
runs-onusing the full runner ID, which is the runner name with thewarp-custom-prefix.
jobs:
build:
runs-on: warp-custom-ci-linux-large
steps:
- uses: actions/checkout@v4
- run: make buildIf you run per-job setup or teardown scripts, bake the paths into the image rather than into every workflow. GitHub's ACTIONS_RUNNER_HOOK_JOB_STARTED and ACTIONS_RUNNER_HOOK_JOB_COMPLETED variables are already used by WarpBuild to orchestrate runners, so set the WARPBUILD_ prefixed alternatives system wide instead, in /etc/environment on Linux or as machine level variables on Windows. Scripts must end in .sh or .ps1, take an absolute path, and a non-zero exit fails the job.
Operations
The maintenance question is how often to rebuild the AMI rather than install per job, and it prices out cleanly.
Take a fleet of 20,000 Linux jobs per month, each spending 90 seconds installing the same toolchain. That is 30,000 runner minutes per month, or 500 EC2 instance hours, spent on work an image could have done once.
| Line item | Volume | WarpBuild fee | Billed by your AWS account |
|---|---|---|---|
| Per-job install, 90 s across 20,000 jobs | 30,000 runner minutes | $60 per month at $0.002 per BYOC runner minute | 500 instance hours plus attached EBS |
Weekly AMI rebuild on warp-ubuntu-latest-x64-4x | 4 builds of 15 minutes | $0.48 per month at $0.008 per minute | None, the build runs on a hosted runner |
Rates are from the WarpBuild pricing page, checked 2026-08-13. The breakeven is low: a weekly rebuild costs $0.48 in runner minutes, which is 240 BYOC runner minutes at $0.002, so the image pays for itself once the pipeline spends more than four hours a month on install steps the image could carry. Below that threshold, per-job installation is the cheaper operating model and the AMI stays a thin base.
Three operational habits keep the rebuild loop boring.
Version the AMI name and never mutate one in place. The template above stamps my-custom-ci-v1.0.0 and tags build_date and version. Registering the new AMI and repointing the runner set makes rollback a two-minute change back to the previous image ID.
Rebuild on a schedule and on dependency change. The workflow above triggers on pushes under packer/. Adding a weekly schedule trigger picks up base image security updates without anyone filing a ticket for it.
Automate the Windows sysprep. The GUI path works for a first image and does not scale. HashiCorp documents sysprep commands for Windows AMIs that run from packer or from a plain PowerShell session.
For the scope question in one paragraph, see does BYOC support custom VM images.
FAQ
What does a custom AMI cost on WarpBuild BYOC?
Nothing extra. The custom VM images documentation states there is no additional cost for using custom VM images, so a runner set on your own AMI bills the same $0.002 per runner minute in WarpBuild fees as one on a stock image, plus the EC2 and EBS charges on your own AWS account (WarpBuild pricing, checked 2026-08-13).
Which packages must be present in a Linux AMI?
curl, wget, bash, jq, and libicu, which the GitHub Actions runner needs as a .NET runtime dependency. The distro also has to use systemd, because the WarpBuild agent runs as a systemd unit, and tar, gzip, coreutils, shadow-utils, and systemd must not be removed from the base image.
Why does a Windows AMI need aria2 and a sysprep pass?
WarpBuild uses aria2 to download runner artifacts on Windows because the default Windows method is slow, so aria2 has to be present and reachable through the system PATH. On AWS the EC2 instance must also be sysprepped before you create the image, otherwise the AMI is not generalized and instances launched from it collide on machine identity.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.