{"id":13487097,"url":"https://github.com/machulav/ec2-github-runner","last_synced_at":"2026-04-10T14:03:26.554Z","repository":{"id":39698923,"uuid":"320320607","full_name":"machulav/ec2-github-runner","owner":"machulav","description":"On-demand self-hosted AWS EC2 runner for GitHub Actions","archived":false,"fork":false,"pushed_at":"2026-02-17T20:27:44.000Z","size":8882,"stargazers_count":838,"open_issues_count":72,"forks_count":380,"subscribers_count":9,"default_branch":"main","last_synced_at":"2026-02-17T21:43:40.853Z","etag":null,"topics":["actions-runner","aws","cicd","ec2","ec2-instance","ec2-runner","github","github-actions","github-actions-runner","github-workflow","javascript","on-demand","runner","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/machulav.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-12-10T15:58:59.000Z","updated_at":"2026-02-17T20:27:50.000Z","dependencies_parsed_at":"2023-12-03T11:21:35.778Z","dependency_job_id":"cdd8022a-1d6a-4c48-8860-1cb29d1ff9ea","html_url":"https://github.com/machulav/ec2-github-runner","commit_stats":{"total_commits":90,"total_committers":33,"mean_commits":2.727272727272727,"dds":0.4444444444444444,"last_synced_commit":"6a96fd4676ce44606c4bc58308af29dfdb345b85"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/machulav/ec2-github-runner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machulav%2Fec2-github-runner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machulav%2Fec2-github-runner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machulav%2Fec2-github-runner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machulav%2Fec2-github-runner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/machulav","download_url":"https://codeload.github.com/machulav/ec2-github-runner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/machulav%2Fec2-github-runner/sbom","scorecard":{"id":609538,"data":{"date":"2025-08-11","repo":{"name":"github.com/machulav/ec2-github-runner","commit":"fb91019e71385fb10dfcbec812b4de8c61589f7b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Code-Review","score":3,"reason":"Found 10/27 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":8,"reason":"9 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 8","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/package.yml:1","Warn: no topLevel permission defined: .github/workflows/pr.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/package.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/machulav/ec2-github-runner/package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/package.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/machulav/ec2-github-runner/package.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pr.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/machulav/ec2-github-runner/pr.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pr.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/machulav/ec2-github-runner/pr.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/pr.yml:20","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-5v72-xg48-5rpm","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T02:18:41.439Z","repository_id":39698923,"created_at":"2025-08-21T02:18:41.439Z","updated_at":"2025-08-21T02:18:41.439Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31236920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-31T09:14:28.471Z","status":"ssl_error","status_checked_at":"2026-03-31T09:14:19.506Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actions-runner","aws","cicd","ec2","ec2-instance","ec2-runner","github","github-actions","github-actions-runner","github-workflow","javascript","on-demand","runner","self-hosted"],"created_at":"2024-07-31T18:00:55.321Z","updated_at":"2026-04-10T14:03:26.546Z","avatar_url":"https://github.com/machulav.png","language":"JavaScript","readme":"# On-demand self-hosted AWS EC2 runner for GitHub Actions\n\n⚠️ If you like the project, please consider [supporting Ukraine](https://prytulafoundation.org/en) in a [war](https://en.wikipedia.org/wiki/Russian_invasion_of_Ukraine) against russian occupants. Any help would be much appreciated!\n\n[\u003cimg src=\"https://user-images.githubusercontent.com/2857712/156607570-8c9fd15b-8b44-41b3-bec3-312267af324f.png\" width=\"500\"\u003e](https://supportukrainenow.org)\n\n(image by [Nina Dzyvulska](https://www.behance.net/ninadz))\n\n---\n\n[![awesome-runners](https://img.shields.io/badge/listed%20on-awesome--runners-blue.svg)](https://github.com/jonico/awesome-runners)\n\nStart your EC2 [self-hosted runner](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners) right before you need it.\nRun the job on it.\nFinally, stop it when you finish.\nAnd all this automatically as a part of your GitHub Actions workflow.\n\n![GitHub Actions self-hosted EC2 runner](docs/images/github-actions-summary.png)\n\nSee [below](#example) the YAML code of the depicted workflow. \u003cbr\u003e\u003cbr\u003e\n\n**Table of Contents**\n\n- [Use cases](#use-cases)\n  - [Access private resources in your VPC](#access-private-resources-in-your-vpc)\n  - [Customize hardware configuration](#customize-hardware-configuration)\n  - [Save costs](#save-costs)\n- [Usage](#usage)\n  - [How to start](#how-to-start)\n  - [Inputs](#inputs)\n  - [Environment variables](#environment-variables)\n  - [Outputs](#outputs)\n  - [Example](#example)\n  - [Advanced: JIT runners](#advanced-jit-runners)\n  - [Advanced: Multi-AZ failover](#advanced-multi-az-failover)\n  - [Advanced: Debug mode](#advanced-debug-mode)\n  - [Real user examples](#real-user-examples)\n- [Self-hosted runner security with public repositories](#self-hosted-runner-security-with-public-repositories)\n- [License Summary](#license-summary)\n\n## Use cases\n\n### Access private resources in your VPC\n\nThe action can start the EC2 runner in any subnet of your VPC that you need - public or private.\nIn this way, you can easily access any private resources in your VPC from your GitHub Actions workflow.\n\nFor example, you can access your database in the private subnet to run the database migration.\n\n### Customize hardware configuration\n\nGitHub provides one fixed hardware configuration for their Linux virtual machines: 2-core CPU, 7 GB of RAM, 14 GB of SSD disk space.\n\nSome of your CI workloads may require more powerful hardware than GitHub-hosted runners provide.\nIn the action, you can configure any EC2 instance type for your runner that AWS provides.\n\nFor example, you may run a c5.4xlarge EC2 runner for some of your compute-intensive workloads.\nOr r5.xlarge EC2 runner for workloads that process large data sets in memory.\n\n### Save costs\n\nIf your CI workloads don't need the power of the GitHub-hosted runners and the execution takes more than a couple of minutes,\nyou can consider running it on a cheaper and less powerful instance from AWS.\n\nAccording to [GitHub's documentation](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/about-self-hosted-runners), you don't need to pay for the jobs handled by the self-hosted runners:\n\n\u003e Self-hosted runners are free to use with GitHub Actions, but you are responsible for the cost of maintaining your runner machines.\n\nSo you will be charged by GitHub only for the time the self-hosted runner start and stop.\nEC2 self-hosted runner will handle everything else so that you will pay for it to AWS, which can be less expensive than the price for the GitHub-hosted runner.\n\n## Usage\n\n### How to start\n\nUse the following steps to prepare your workflow for running on your EC2 self-hosted runner:\n\n**1. Prepare IAM user with AWS access keys**\n\n1. Create new AWS access keys for the new or an existing IAM user with the following least-privilege minimum required permissions:\n\n   ```json\n   {\n     \"Version\": \"2012-10-17\",\n     \"Statement\": [\n       {\n         \"Effect\": \"Allow\",\n         \"Action\": [\n           \"ec2:RunInstances\",\n           \"ec2:TerminateInstances\",\n           \"ec2:DescribeInstances\",\n           \"ec2:DescribeInstanceStatus\"\n         ],\n         \"Resource\": \"*\"\n       }\n     ]\n   }\n   ```\n\n   If you use the `runner-debug` input to enable debug logging, you will also need to allow the `ec2:GetConsoleOutput` permission so the action can poll the EC2 serial console output during startup:\n\n   ```json\n   {\n     \"Version\": \"2012-10-17\",\n     \"Statement\": [\n       {\n         \"Effect\": \"Allow\",\n         \"Action\": [\n           \"ec2:GetConsoleOutput\"\n         ],\n         \"Resource\": \"*\"\n       }\n     ]\n   }\n   ```\n\n   If you plan to attach an IAM role to the EC2 runner with the `iam-role-name` parameter, you will need to allow additional permissions:\n\n   ```json\n   {\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": [\n          \"ec2:ReplaceIamInstanceProfileAssociation\",\n          \"ec2:AssociateIamInstanceProfile\"\n        ],\n        \"Resource\": \"*\"\n      },\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": \"iam:PassRole\",\n        \"Resource\": \"*\"\n      }\n    ]\n   }\n   ```\n\n   If you use the `aws-resource-tags` parameter, you will also need to allow the permissions to create tags:\n\n   ```json\n   {\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": [\n          \"ec2:CreateTags\"\n        ],\n        \"Resource\": \"*\",\n        \"Condition\": {\n          \"StringEquals\": {\n            \"ec2:CreateAction\": \"RunInstances\"\n          }\n        }\n      }\n    ]\n   }\n   ```\n\n   These example policies above are provided as a guide. They can and most likely should be limited even more by specifying the resources you use.\n\n\n2. Add the keys to GitHub secrets.\n3. Use the [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) action to set up the keys as environment variables.\n\n\u003e [!IMPORTANT]\n\u003e If you are planning on using Spot instances for your runner, AWS uses a service-linked role to provision the instances.\n\u003e\n\u003e For this to work, at least one of the following must be true:\n\u003e - The service-linked role exists already. This happens if you request a Spot instance via the AWS Console interface.\n\u003e - You create the service-linked role via the Console, AWS CLI or AWS API.\n\u003e - You grant the IAM role above permissions to create the service-linked role at runtime.\n\u003e See the docs [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create-service-linked-role.html) and [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/service-linked-roles-spot-instance-requests.html) for more details.\n\n**2. Prepare GitHub personal access token**\n\n1. Create a new GitHub personal access token with the `repo` scope.\n   The action will use the token for self-hosted runners management in the GitHub account on the repository level.\n2. Add the token to GitHub secrets.\n\n**3. Prepare EC2 image**\n\n1. Create a new EC2 instance based on any Linux distribution you need.\n2. Connect to the instance using SSH, install `docker` and `git`, then enable `docker` service.\n\n   For Amazon Linux 2023:\n\n   ```shell\n    sudo dnf update -y \u0026\u0026 \\\n    sudo dnf install docker git libicu -y \u0026\u0026 \\\n    sudo systemctl enable docker\n   ```\n\n   For Amazon Linux 2:\n\n   ```shell\n    sudo yum update -y \u0026\u0026 \\\n    sudo yum install docker git libicu -y \u0026\u0026 \\\n    sudo systemctl enable docker\n   ```\n\n   For other Linux distributions, it could be slightly different.\n\n3. Install any other tools required for your workflow.\n4. Create a new EC2 image (AMI) from the instance.\n5. Remove the instance if not required anymore after the image is created.\n\n\u003e **Important:** If your AMI was created from an instance that previously ran a GitHub Actions runner, make sure to delete the stale runner configuration files (`.runner`, `.credentials`, `.credentials_rsaparams`) from the runner directory before creating the AMI. The action handles this automatically, but a clean AMI avoids unnecessary warnings.\n\nAlternatively, you can use a vanilla EC2 AMI and set up the dependencies via `pre-runner-script` or the `packages` input in the workflow YAML file.\n\n\u003e **Compatibility note:** This action uses a `#cloud-boothook` user-data format to ensure the setup script runs during cloud-init's init stage. This is compatible with Amazon Linux 2, Amazon Linux 2023, Ubuntu, and other distributions that support cloud-init. The boothook approach avoids issues with some AMIs where `cloud_final_modules` (used by `runcmd`) may be empty or misconfigured.\n\n**4. Prepare VPC with subnet and security group**\n\n1. Create a new VPC and a new subnet in it.\n   Or use the existing VPC and subnet.\n2. Create a new security group for the runners in the VPC.\n   Only **outbound** traffic on port TCP/443 is required to pull jobs from GitHub.\n   No inbound traffic is required for this purpose, but if your workflow needs to access external repositories or internal SSH, other ports like TCP/22, TCP/80, etc ... may be required.\n\n**5. Configure the GitHub workflow**\n\n1. Create a new GitHub Actions workflow or edit the existing one.\n2. Use the documentation and example below to configure your workflow.\n3. Please don't forget to set up a job for removing the EC2 instance at the end of the workflow execution.\n   Otherwise, the EC2 instance won't be removed and continue to run even after the workflow execution is finished.\n\nNow you're ready to go!\n\n### Inputs\n\n| Name | Required | Description |\n| --- | --- | --- |\n| `mode` | Always required. | Specify here which mode you want to use: \u003cbr\u003e - `start` - to start a new runner; \u003cbr\u003e - `stop` - to stop the previously created runner. |\n| `github-token` | Always required. | GitHub Personal Access Token with the `repo` scope assigned. |\n| `ec2-image-id` | Required if you use the `start` mode and don't provide `availability-zones-config`. | EC2 Image Id (AMI). The new runner will be launched from this image. Compatible with Amazon Linux 2, Amazon Linux 2023, and Ubuntu images. |\n| `ec2-instance-type` | Required if you use the `start` mode. | EC2 Instance Type. |\n| `subnet-id` | Required if you use the `start` mode and don't provide `availability-zones-config`. | VPC Subnet Id. The subnet should belong to the same VPC as the specified security group. |\n| `security-group-id` | Required if you use the `start` mode and don't provide `availability-zones-config`. | EC2 Security Group Id. The security group should belong to the same VPC as the specified subnet. Only outbound traffic for port 443 is required. No inbound traffic is required. |\n| `label` | Required if you use the `stop` mode. | Name of the unique label assigned to the runner. The label is provided by the output of the action in the `start` mode. |\n| `ec2-instance-id` | Required if you use the `stop` mode. | EC2 Instance Id of the created runner. The id is provided by the output of the action in the `start` mode. |\n| `availability-zones-config` | Optional. Used only with the `start` mode. | JSON string array of objects for multi-AZ failover. Each object must contain `imageId`, `subnetId`, and `securityGroupId`. Optionally specify `region` per entry (defaults to `AWS_REGION`). When provided, takes precedence over individual `ec2-image-id`, `subnet-id`, and `security-group-id` parameters. See [Multi-AZ failover](#advanced-multi-az-failover). |\n| `iam-role-name` | Optional. Used only with the `start` mode. | IAM role name to attach to the created EC2 runner. This allows the runner to have permissions to run additional actions within the AWS account. Requires additional AWS permissions (see above). |\n| `aws-resource-tags` | Optional. Used only with the `start` mode. | Specifies tags to add to the EC2 instance and any attached storage. This field is a stringified JSON array of tag objects, each containing a `Key` and `Value` field. Requires additional AWS permissions (see above). |\n| `runner-home-dir` | Optional. Used only with the `start` mode. | Specifies a directory where pre-installed actions-runner software and scripts are located. When set, the action skips downloading the runner and uses the pre-installed version. |\n| `pre-runner-script` | Optional. Used only with the `start` mode. | Specifies bash commands to run before the runner starts. Useful for installing dependencies with apt-get, yum, dnf, etc. |\n| `market-type` | Optional. Used only with the `start` mode. | Accepts only the value `spot`. If set, the runner will be launched as a Spot instance. If omitted, an on-demand instance is used. |\n| `block-device-mappings` | Optional. Used only with the `start` mode. | JSON string specifying the block device mappings for the EC2 instance. See [AWS BlockDeviceMapping docs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html). |\n| `metadata-options` | Optional. Used only with the `start` mode. | JSON string specifying the instance metadata options. Example: `'{\"HttpTokens\": \"required\", \"HttpEndpoint\": \"enabled\", \"HttpPutResponseHopLimit\": 2}'` |\n| `packages` | Optional. Used only with the `start` mode. | JSON array of packages to install during boot via `yum` or `apt-get`. Example: `'[\"git\", \"docker.io\", \"nodejs\"]'`. Default: `'[]'` |\n| `run-runner-as-service` | Optional. Used only with the `start` mode. | When `true`, starts the runner as a systemd service using `svc.sh` instead of `run.sh`. Default: `false` |\n| `run-runner-as-user` | Optional. Used only with the `start` mode. | Specify a user under whom the runner should run. The runner files will be `chown`'d to this user and the runner process will be started via `runuser -u \u003cuser\u003e`. |\n| `use-jit` | Optional. Used only with the `start` mode. | Enable JIT (Just-In-Time) runner configuration. Uses GitHub's `generate-jitconfig` API instead of the traditional `registration-token` approach. JIT runners are single-use and auto-deregister after completing one job. Incompatible with `run-runner-as-service: true`. Default: `false`. See [JIT runners](#advanced-jit-runners). |\n| `runner-group-id` | Optional. Used only with the `start` mode. | The ID of the runner group to register the JIT runner in. Defaults to `1` (the \"Default\" runner group). Only used when `use-jit` is `true`. |\n| `runner-debug` | Optional. Used only with the `start` mode. | Enable verbose debug logging for the runner setup. When `true`, the action logs detailed instance info, step-by-step script execution, and polls the EC2 serial console output during startup. Requires the `ec2:GetConsoleOutput` IAM permission (see above). Default: `false`. See [Debug mode](#advanced-debug-mode). |\n| `startup-quiet-period-seconds` | Optional. | Quiet period in seconds before checking for runner registration. Default: `30` |\n| `startup-retry-interval-seconds` | Optional. | Retry interval in seconds for checking runner registration. Default: `10` |\n| `startup-timeout-minutes` | Optional. | Timeout in minutes for runner registration. Default: `5` |\n| `ec2-volume-size` | Optional. | EC2 volume size in GB. Uses the AWS/AMI default if not provided. |\n| `ec2-device-name` | Optional. | EC2 block device name. Default: `/dev/sda1` |\n| `ec2-volume-type` | Optional. | EC2 block device type (e.g. `gp3`, `gp2`, `io1`). |\n\n### Environment variables\n\nIn addition to the inputs described above, the action also requires the following environment variables to access your AWS account:\n\n- `AWS_DEFAULT_REGION`\n- `AWS_REGION`\n- `AWS_ACCESS_KEY_ID`\n- `AWS_SECRET_ACCESS_KEY`\n\nWe recommend using [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) action right before running the step for creating a self-hosted runner. This action perfectly does the job of setting the required environment variables.\n\n### Outputs\n\n| \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;Name\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp; | Description                                                                                                                                                                                                                               |\n| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `label`                                                                                                                                                                      | Name of the unique label assigned to the runner. \u003cbr\u003e\u003cbr\u003e The label is used in two cases: \u003cbr\u003e - to use as the input of `runs-on` property for the following jobs; \u003cbr\u003e - to remove the runner from GitHub when it is not needed anymore. |\n| `ec2-instance-id`                                                                                                                                                            | EC2 Instance Id of the created runner. \u003cbr\u003e\u003cbr\u003e The id is used to terminate the EC2 instance when the runner is not needed anymore.                                                                                                       |\n| `region`                                                                                                                                                                      | AWS region where the EC2 instance was created. \u003cbr\u003e\u003cbr\u003e This is useful for subsequent AWS operations on the instance.                                                                                                                     |\n\n\n### Example\n\nThe workflow showed in the picture above and declared in `do-the-job.yml` looks like this:\n\n```yml\nname: do-the-job\non: pull_request\njobs:\n  start-runner:\n    name: Start self-hosted EC2 runner\n    runs-on: ubuntu-latest\n    outputs:\n      label: ${{ steps.start-ec2-runner.outputs.label }}\n      ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}\n    steps:\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}\n          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n          aws-region: ${{ secrets.AWS_REGION }}\n      - name: Start EC2 runner\n        id: start-ec2-runner\n        uses: machulav/ec2-github-runner@v2\n        with:\n          mode: start\n          github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n          ec2-image-id: ami-123\n          ec2-instance-type: t3.nano\n          subnet-id: subnet-123\n          security-group-id: sg-123\n          iam-role-name: my-role-name # optional, requires additional permissions\n          aws-resource-tags: \u003e # optional, requires additional permissions\n            [\n              {\"Key\": \"Name\", \"Value\": \"ec2-github-runner\"},\n              {\"Key\": \"GitHubRepository\", \"Value\": \"${{ github.repository }}\"}\n            ]\n          block-device-mappings: \u003e # optional, to customize EBS volumes\n            [\n              {\"DeviceName\": \"/dev/sda1\", \"Ebs\": {\"VolumeSize\": 100, \"VolumeType\": \"gp3\"}}\n            ]\n  do-the-job:\n    name: Do the job on the runner\n    needs: start-runner # required to start the main job when the runner is ready\n    runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner\n    steps:\n      - name: Hello World\n        run: echo 'Hello World!'\n  stop-runner:\n    name: Stop self-hosted EC2 runner\n    needs:\n      - start-runner # required to get output from the start-runner job\n      - do-the-job # required to wait when the main job is done\n    runs-on: ubuntu-latest\n    if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs\n    steps:\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v4\n        with:\n          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}\n          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n          aws-region: ${{ secrets.AWS_REGION }}\n      - name: Stop EC2 runner\n        uses: machulav/ec2-github-runner@v2\n        with:\n          mode: stop\n          github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n          label: ${{ needs.start-runner.outputs.label }}\n          ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}\n```\n\n### Advanced: JIT runners\n\nJIT (Just-In-Time) runners use GitHub's `generate-jitconfig` API to create single-use runners that automatically deregister after completing one job. This eliminates the need for `config.sh` and simplifies cleanup.\n\nJIT runners skip the traditional registration-token flow entirely. Instead, the encoded JIT config is passed directly to `./run.sh --jitconfig \u003cconfig\u003e`. The runner self-destructs after the job completes, so `stop` mode only terminates the EC2 instance (no GitHub runner removal needed).\n\n\u003e **Note:** JIT mode is incompatible with `run-runner-as-service: true` since JIT runners are inherently single-use.\n\n```yml\n      - name: Start EC2 runner\n        id: start-ec2-runner\n        uses: machulav/ec2-github-runner@v2\n        with:\n          mode: start\n          github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n          ec2-image-id: ami-123\n          ec2-instance-type: t3.nano\n          subnet-id: subnet-123\n          security-group-id: sg-123\n          use-jit: true\n          runner-group-id: 1  # optional, defaults to the \"Default\" runner group\n```\n\n### Advanced: Multi-AZ failover\n\nThe `availability-zones-config` input allows you to specify multiple availability zone configurations. The action will try each one in sequence until an instance is successfully launched. This is useful for handling capacity issues or spot instance unavailability in a specific AZ.\n\nEach configuration object requires `imageId`, `subnetId`, and `securityGroupId`. You can optionally specify a `region` per entry; if omitted, the `AWS_REGION` environment variable is used.\n\n```yml\n      - name: Start EC2 runner\n        id: start-ec2-runner\n        uses: machulav/ec2-github-runner@v2\n        with:\n          mode: start\n          github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n          ec2-instance-type: t3.nano\n          market-type: spot\n          availability-zones-config: \u003e\n            [\n              {\"imageId\": \"ami-123\", \"subnetId\": \"subnet-aaa\", \"securityGroupId\": \"sg-111\"},\n              {\"imageId\": \"ami-456\", \"subnetId\": \"subnet-bbb\", \"securityGroupId\": \"sg-222\", \"region\": \"us-west-2\"},\n              {\"imageId\": \"ami-789\", \"subnetId\": \"subnet-ccc\", \"securityGroupId\": \"sg-333\", \"region\": \"eu-west-1\"}\n            ]\n```\n\n### Advanced: Debug mode\n\nWhen a runner fails to register, it can be difficult to diagnose the issue because user-data scripts execute on the remote EC2 instance. The `runner-debug` input enables verbose logging to help with troubleshooting.\n\nWhen `runner-debug: true` is set, the action will:\n\n1. **Inject detailed echo statements** into the setup script on the instance — logging each step (architecture detection, runner download, config.sh execution, etc.)\n2. **Poll the EC2 serial console output** during the registration wait loop, streaming new output to the GitHub Actions log as it appears\n3. **Log the full user-data script** content so you can see exactly what was sent to the instance\n\nThis requires the `ec2:GetConsoleOutput` IAM permission. Add the following to your IAM policy:\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"ec2:GetConsoleOutput\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n```\n\n\u003e **Note:** EC2 serial console output takes 2-5 minutes to become available after instance launch and may not capture all output from user-data scripts. For full script logs, SSH into the instance and check `/tmp/runner-setup.log`.\n\n```yml\n      - name: Start EC2 runner\n        id: start-ec2-runner\n        uses: machulav/ec2-github-runner@v2\n        with:\n          mode: start\n          github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}\n          ec2-image-id: ami-123\n          ec2-instance-type: t3.nano\n          subnet-id: subnet-123\n          security-group-id: sg-123\n          runner-debug: true\n          startup-timeout-minutes: 10  # increase timeout when debugging\n```\n\n### Real user examples\n\nIn [this discussion](https://github.com/machulav/ec2-github-runner/discussions/19), you can find feedback and examples from the users of the action.\n\nIf you use this action in your workflow, feel free to add your story there as well 🙌\n\n## Self-hosted runner security with public repositories\n\n\u003e We recommend that you do not use self-hosted runners with public repositories.\n\u003e\n\u003e Forks of your public repository can potentially run dangerous code on your self-hosted runner machine by creating a pull request that executes the code in a workflow.\n\nPlease find more details about this security note on [GitHub documentation](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/about-self-hosted-runners#self-hosted-runner-security-with-public-repositories).\n\n## License Summary\n\nThis code is made available under the [MIT license](LICENSE).\n","funding_links":[],"categories":["JavaScript","The matrix (might be better readable on [GitHub pages](https://jonico.github.io/awesome-runners/))"],"sub_categories":["A word about self-hosted action runner images / virtual environments and how to test locally"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmachulav%2Fec2-github-runner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmachulav%2Fec2-github-runner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmachulav%2Fec2-github-runner/lists"}