Self-hosted service
machulav/ec2-github-runner avatar
machulav/ec2-github-runner

ec2-github-runner starts an instance for one job, and every IAM example it ships says Resource star

On-demand self-hosted AWS EC2 runner for GitHub Actions

860 stars388 forksJavaScriptMIT

At a glance

What is it?
A JavaScript GitHub Action that boots an EC2 instance, registers it as a self-hosted runner, runs the job and terminates the instance, aimed at jobs that need a private subnet or more than GitHub's fixed Linux VM. The permission templates are the part to read closely, and so is the dependency list that puts a deprecated bundler in production dependencies.
Who is it for?
ec2-github-runner earns its place when a job needs a private subnet, a GPU-shaped instance type, or a runtime that costs less than a GitHub-hosted runner billed by the minute. Before you wire it up, narrow the permissions yourself, because every policy in the document is written with a wildcard resource and the text says as much, and decide deliberately whether you want the action's user able to create an AWS service-linked role at runtime for Spot capacity.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 148 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Four policy templates, every one of them scoped to a wildcard

The setup instructions start from a least-privilege baseline: create AWS access keys for a new or existing IAM user and allow four actions, ec2:RunInstances, ec2:TerminateInstances, ec2:DescribeInstances and ec2:DescribeInstanceStatus. The policy is written as JSON with a version of 2012-10-17 and a resource of `*`, which means every instance you own rather than the instances you have in mind. Three further templates widen it conditionally, and each one is tied to an input rather than to a feature. Polling the EC2 serial console during startup, which the runner-debug input turns on, needs ec2:GetConsoleOutput. Attaching a role through the iam-role-name input needs ec2:ReplaceIamInstanceProfileAssociation and ec2:AssociateIamInstanceProfile plus iam:PassRole, the last of which is the one that can hand a role to something else entirely. Tagging through aws-resource-tags needs ec2:CreateTags with a condition that the create action equals RunInstances. The document closes the list by saying these examples can and most likely should be limited further by naming the resources you use.

Spot capacity needs a service-linked role, and runtime creation is one of the options

There is a warning block between the credentials setup and the GitHub token, and it exists because Spot requests fail in a specific way. AWS provisions Spot instances using a service-linked role, and the action will not create it for you. Three conditions make it work: the role already exists, which happens if you have ever requested a Spot instance through the AWS console; you create it yourself through the console, the CLI or the API; or you grant your IAM role permission to create the service-linked role at runtime. That third option is the one worth thinking about, because creating a service-linked role is an account-level IAM operation with its own trust policy, so an action whose stated job is launching instances now needs the right to change what roles exist in the account. Anyone choosing that path should read what they are granting, because it is broader than running an instance.

A repository-level permission, requested with an account-wide token scope

The GitHub side is one personal access token with the `repo` scope, added to secrets, and the document states the action uses it for self-hosted runner management in the account at the repository level. That mismatch is the thing to notice: the work being done is scoped to one repository, while the token scope that carries it is the classic token scope covering private repository content. A fine-grained token limited to the one repository and to actions permissions would express the same intent with a smaller blast radius, and the document does not mention that option. The AWS keys take a second path, since they are not injected directly but are set up as environment variables through the aws-actions/configure-aws-credentials action, which is the conventional way to keep cloud credentials out of the step body.

The image preparation ends one command into the Amazon Linux 2 example

The runner image is whatever Linux distribution you choose, and the requirement is small: connect over SSH, install Docker and git, and enable the Docker service. The Amazon Linux 2023 example is one shell line:

shell
sudo dnf update -y && \
sudo dnf install docker git libicu -y && \
sudo systemctl enable docker

Two details sit in that line without explanation. The package list includes libicu, which is a Unicode library rather than anything Docker needs at runtime, so whatever pulls it in is a build-time dependency of a job rather than a documented runtime requirement. And the Amazon Linux 2 example, which begins immediately after, opens a code fence and then the text ends, so the commands for the older Amazon Linux image are not present in the document. The rest of the runner bootstrap, including anything about the runner binary itself, is not visible here either, and the linked documentation is where that would have to come from.

The production dependency list includes the bundler ncc replaced

The manifest carries no version field at all, and its dependency set has an odd shape. Four dependencies are ordinary: the Actions toolkit core, the GitHub toolkit, the EC2 client and lodash. The fifth is ncc at a caret range on 0.3.6, the bundler package that was superseded and renamed, sitting in runtime dependencies. The current bundler, @vercel/ncc, is present too, but as a development dependency at 0.38.1, and the build script is `ncc build ./src/index.js`, which is the name the old package claims. So the command that produces the committed bundle in dist/ resolves to the deprecated tool rather than the maintained one. The rest of the development set is more uneven still: eslint at 7.32.0, which predates the flat config era, next to jest at 30.2.0. The lint and formatting configuration at the top level uses the older YAML filenames, and the repository URL is written with an ssh git address.

The committed env template lists the inputs and misspells its own instructions

Development happens against a committed template file whose first line is a warning: do not define production secrets in this file or in any other committed file. The structure is a convention worth copying. Variables prefixed AWS_ point at a test account, variables prefixed INPUT_ simulate the inputs a workflow would pass, and GITHUB_REPOSITORY supplies the owner and repository context in the owner/repo form. That prefix convention makes the input names legible from the template, and they cover mode, the GitHub token, image id, instance type, subnet, security group, label, instance id, volume size, device name, volume type, a JIT switch and a runner group id. The instructions above them contain two typos, one in the word for owner and one in the word for format, which is the level of care the rest of the file does not quite maintain.

A tag named v2 from 2021 sits beside two releases twenty minutes apart

The release history has a shape worth noting. v2.6.0 and v2.6.1 were both published on 2026-04-10, twenty-one minutes apart, and the earlier of the pair carries a version number that reads like a feature release while the later reads like a patch. Below them sits a tag named `v2` dated 2021-06-29, which shares the major version those releases already passed, so anyone selecting a version by tag has a three-year-old artifact sitting inside the current series. The repository's last push is dated 2026-05-08. The table of contents promises a good deal more than the visible text covers, including a section on JIT runners, multi-AZ failover, a debug mode, real user examples, a section on self-hosted runner security with public repositories, and a license summary rather than a licence statement. Those belong to the part of the document that is not shown here.

Editorial conclusion

ec2-github-runner earns its place when a job needs a private subnet, a GPU-shaped instance type, or a runtime that costs less than a GitHub-hosted runner billed by the minute. Before you wire it up, narrow the permissions yourself, because every policy in the document is written with a wildcard resource and the text says as much, and decide deliberately whether you want the action's user able to create an AWS service-linked role at runtime for Spot capacity. Keep the GitHub token as narrow as you can, pin the action to a tag rather than a branch, and note that the repository bundles with a deprecated tool whose build script is the one that runs.

Frequently asked questions

What AWS permissions does the ec2-github-runner action need?

A baseline of ec2:RunInstances, ec2:TerminateInstances, ec2:DescribeInstances and ec2:DescribeInstanceStatus, all written with a wildcard resource. Inputs add more: ec2:GetConsoleOutput for the debug input, instance profile association plus iam:PassRole for iam-role-name, and ec2:CreateTags conditioned on the create action being RunInstances for aws-resource-tags.

What does ec2-github-runner need from GitHub?

A personal access token with the repo scope, stored as a secret. The document says the action uses it for self-hosted runner management in the account at the repository level. AWS keys are supplied separately and exported as environment variables through the aws-actions/configure-aws-credentials action.

Can I use Spot instances with this action?

Yes, with one prerequisite. AWS provisions Spot capacity through a service-linked role that must already exist, must be created beforehand by you, or must be creatable at runtime by the permissions you grant your action's role.

What has to be on the EC2 image used as a runner?

Docker and git, with the Docker service enabled, on whichever Linux distribution you build the image from. The Amazon Linux 2023 example updates packages, installs docker, git and libicu, and enables the service with systemctl.

How is an EC2 self-hosted runner charged?

GitHub bills only for the time the runner takes to start and stop, since self-hosted runners are free to use with Actions. The instance itself is billed by AWS, which the document presents as potentially cheaper than a GitHub-hosted runner for long, light workloads.

Official sources

  1. Issues
  2. License: MIT
  3. machulav/ec2-github-runner on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/machulav-ec2-github-runner.svg)](https://hysenlabs.com/projects/machulav-ec2-github-runner)