# Self-hosted GitHub Actions runners on AWS

There are five common ways to run GitHub Actions jobs in your own AWS account. What each runs a job on, what it keeps running, what it costs, and where it breaks.

October 26, 2026, by Supercorp.

Running GitHub Actions jobs in your own AWS account buys three things: AWS's prices instead of GitHub's, your own network and IAM roles, and machines of any size. There are five common ways to do it. They differ in what a job runs on, what you keep running between jobs, and what breaks first.

## The five ways at a glance

| Way | A job runs on | Between jobs | Docker in a job |
|---|---|---|---|
| A runner on an EC2 machine | the same machine, job after job | the machine, all the time | yes |
| Runners in ECS on Fargate | a container | the containers you keep | no |
| AWS CodeBuild | a CodeBuild build | nothing | in privileged mode, set on the project |
| Actions Runner Controller on EKS | a pod | the cluster | in privileged mode |
| A fresh EC2 machine for each job | a machine of its own | a small function | yes |

## 1. A runner on an EC2 machine

Start an EC2 machine, install GitHub's runner on it as GitHub's [guide to adding self-hosted runners](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/add-runners) shows, and it takes jobs. It is the quickest way to begin, and it has two costs.

The machine is paid for all the time. A 4-CPU, 16 GB m8a.xlarge on demand in us-east-1 is about $0.24 an hour, or about $178 for a month of being on, whether jobs run or not.

And it is the same machine for every job. Files, caches, Docker images and running processes from one job are there for the next. GitHub's [reference for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners) is direct about it:

> GitHub recommends implementing autoscaling with ephemeral self-hosted runners; autoscaling with persistent self-hosted runners is not recommended.

An ephemeral runner takes one job and is removed. Everything below is a way to get that.

## 2. Runners in ECS on Fargate

A runner fits in a container, so ECS looks like a natural home: a service that keeps a number of runner containers going, on Fargate so there are no machines to look after.

The catch is Docker. AWS's [Fargate security considerations](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-security-considerations.html) say "privileged containers or access are currently unavailable on Fargate. This will affect uses cases such as running Docker in Docker." A job that runs `docker build`, a service container or a container action cannot run in a Fargate runner. Jobs that only need a language toolchain are fine. ECS on EC2 machines allows privileged containers, at the price of managing those machines again.

A runner container that takes one job also has to be replaced after each one, which means something listening for jobs and starting tasks: an autoscaler, which is the work of option 5 inside a container.

## 3. AWS CodeBuild

CodeBuild can run GitHub Actions jobs itself. You make a CodeBuild project with a webhook, and a job names the project in its `runs-on`, with the run's id ([AWS's tutorial](https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner.html)):

```yaml
runs-on: codebuild-myProject-${{ github.run_id }}-${{ github.run_attempt }}
```

Each job becomes a CodeBuild build, and nothing runs between builds. Four things to know:

- **The price is CodeBuild's, not EC2's.** A Linux Medium build (4 vCPUs, 8 GiB) is $0.010 a minute on demand in us-east-1, close to GitHub's $0.012 for its 4-core runner, and [billed](https://aws.amazon.com/codebuild/pricing/) "from the time you submit your build until your build is terminated, rounded up to the nearest minute".
- **The machine is CodeBuild's image**, Amazon Linux or Ubuntu with CodeBuild's tools, chosen with label overrides such as `image:` and `instance-size:`, not GitHub's runner image. Workflows that lean on tools GitHub's image has may need setup steps.
- **A build has a timeout:** 60 minutes unless the project says otherwise, and at most 36 hours ([AWS's project settings](https://docs.aws.amazon.com/codebuild/latest/userguide/create-project.html)). A job longer than an hour needs it raised.
- **The label must name the project exactly.** AWS's tutorial warns that if it does not match, "CodeBuild will not process the webhook and the GitHub Actions workflow might hang".

CodeBuild is the choice with the least to run yourself. It is not the cheap one.

## 4. Actions Runner Controller on EKS

Actions Runner Controller (ARC) is GitHub's own autoscaler, and it runs runners as pods in a Kubernetes cluster. On AWS that means EKS. GitHub's [support page for ARC](https://docs.github.com/en/actions/concepts/runners/support-for-arc) recommends "staff with expert-level knowledge of container orchestration", and jobs that use Docker need Docker-in-Docker in privileged mode, or a Kubernetes mode in which every job names a container.

If you already run EKS and have people who know it, ARC is a good answer. If you do not, it is a cluster to run for the sake of CI. [SuperCI vs Actions Runner Controller](/compare/actions-runner-controller) goes further.

## 5. A fresh EC2 machine for each job

The last way does what GitHub recommends with plain machines: when a job is queued, start an EC2 machine registered for that one job, and end it when the job is done. The job has a whole machine, so Docker, services and container jobs work as they do on GitHub's runners. Nothing runs between jobs but a small function that hears GitHub's webhooks.

On spot machines it is also the cheapest of the five. A 4-CPU, 16 GB spot machine in us-east-1, with its disk and public address, came to about $0.0014 a minute on 12 October 2026, against GitHub's $0.012. The [calculator](/calculator) has every size.

You can build it on GitHub's [just-in-time runner API](https://docs.github.com/en/rest/actions/self-hosted-runners), or use one of three tools that do it:

- **[terraform-aws-github-runner](https://github.com/github-aws-runners/terraform-aws-github-runner)**, an open-source Terraform module: Lambda functions start EC2 machines as jobs arrive. See [how it compares](/compare/terraform-aws-github-runner).
- **[RunsOn](https://runs-on.com)**, a CloudFormation stack, free for non-commercial use and licensed otherwise. See [how it compares](/compare/runs-on).
- **SuperCI**, which we make: open source under the MIT licence, set up from a dashboard on your computer, and it also runs jobs on Cloudflare and Modal, and GitLab CI jobs.

## Which to choose

- **A few jobs a day, and you want it today:** a runner on one EC2 machine, cleaned up by hand now and then. Move on when the bill or a leftover file bites.
- **You want nothing to run yourself and the price is fine:** CodeBuild.
- **You already run EKS:** Actions Runner Controller.
- **Everything else:** a fresh machine per job, on spot.

## With SuperCI

```sh
npx @superci/cli dashboard
```

That opens a dashboard on your own computer. Sign in with AWS, and it puts a small control plane in your account (one Lambda function and a table) and makes a GitHub App that is yours. Then one line in a workflow:

```yaml
jobs:
  test:
    runs-on: superci/cpu=4/ram=16
```

Each job gets a fresh spot machine of that size, registered for exactly that job, and the machine ends with the job. When AWS takes a spot machine back, the job runs again off spot, on an on-demand machine unless you set another order. [Getting started](/docs/getting-started) has the rest, and [What your AWS account allows](/docs/aws-accounts) covers accounts with rules of their own.

## One caution

Whatever you choose, keep public repositories off your runners unless you mean it. GitHub [recommends](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/manage-access) using self-hosted runners only with private repositories, "because forks of your public repository can potentially run dangerous code on your self-hosted runner machine". SuperCI refuses public repositories until you allow one by name, and refuses forks' pull requests even then.
