GitHub Actions Heroku-hosted Docker Runner

This project defines a Dockerfile to run a self-hosted Github Actions runner.

The runner is hosted on Heroku as a docker image via heroku.yml.

How it works

Once the self-hosted runner is running on Heroku you can start adding workflows to your private GitHub repositories (see this project template as an example) to automate Heroku Review Apps creation and Heroku Apps deploys using the following actions:

GitHub Actions on Heroku

The Heroku self-hosted runner will autoregister with your GitHub Org (1). When git push / pull-request commands are executed toward your private GitHub repository (2) your workflows will trigger the code fetch from your repository (3).
The source code will be automatically compressed and uploaded to a temporary Heroku bucket (4) then built and deployed to your apps (5). When a pull request is created a new Review App is created and once it is closed the associated Review App is automatically removed.

Disclaimer

The author of this article makes any warranties about the completeness, reliability and accuracy of this information. Any action you take upon the information of this website is strictly at your own risk, and the author will not be liable for any losses and damages in connection with the use of the website and the information provided. None of the items included in this repository form a part of the Heroku Services.

Quick Start

Things you'll need

The setup requires configurations in both your GitHub organization and your Heroku organization.

You will switch between them throughout the following instructions.

  1. In GitHub, enable GitHub Actions for your organization

  2. In GitHub, add your Heroku Private Space's outbound IP addresses to your organization's allow list (see this article) and check the Enable IP allow list box

  3. In GitHub, create a personal access token with admin:org and repo scopes (see these articles 1, 2) or a fine-grained token with "Self-hosted runners" organization permissions (read and write) (see https://github.blog/security/application-security/introducing-fine-grained-personal-access-tokens-for-github/#creating-personal-access-tokens). This token will be used only to configure the runner. Authenticated users must have admin access to the organization. (see https://docs.github.com/en/enterprise-cloud@latest/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization).

    Don't forget to authorize your access token to SSO to your organization

  4. In Heroku, create a new app in your private space

  5. In Heroku, add two configuration variables to the new app

    • GITHUB_ACCESS_TOKEN with the token you created previously
    • GITHUB_ORGANIZATION with the name of your organization
  6. From the Heroku CLI login as the service/automation Heroku user to create an API token (SSO users cannot create tokens)

  7. Generate a new Heroku API key

    • heroku authorizations:create -d "GitHub self-hosted actions automation" --expires-in=<set expiration time in seconds> setting an adequate expiration time
  8. Login as the Heroku administrator and grant the service/automation Heroku user access to view and deploy to your new app

  9. In GitHub, add an organization secret to store the Heroku information

  10. In GitHub, add an organization/repository variable to store the Heroku self-hosted runner app name

  11. Locally, clone and deploy this repository to your Heroku app or click on the Heroku Button

    Deploy to Heroku

    git clone https://github.com/abernicchia-heroku/heroku-github-actions-runner.git
    heroku git:remote --app HEROKU_SELFHOSTED_RUNNER_APPNAME
    heroku apps:stacks:set --app HEROKU_SELFHOSTED_RUNNER_APPNAME container
    git push heroku main
  12. In Heroku, scale your runner resource appropriate for your expected usage

Voila!

Now when GitHub Action workflows are launched by your repositories, GitHub will orchestrate with your Heroku-hosted runner to do the work just as if you were using GitHub-hosted runners.

Keeping Your Runner Updated

GitHub frequently releases updates to the GitHub Action runner package.

If you don't keep the package up-to-date within 30 days then GitHub won't enqueue jobs.

This project includes a workflow that can be run manually or once a week. It will rebuild the docker container and download the latest updates automatically and it will deploy automatically to your Heroku self-hosted runner app.

To take advantage of this automation you need to fork or mirror this repository to your private organisation's repository and enable workflows run.

GitHub Runner Script Usage

The following is how to use the config.sh and run.sh scripts installed by the runner package (see https://github.com/actions/runner/blob/main/src/Runner.Listener/Runner.cs).

Commands:
 ./config.sh         Configures the runner
 ./config.sh remove  Unconfigures the runner
 ./run.sh            Runs the runner interactively. Does not require any options.

Options:
 --help     Prints the help for each command
 --version  Prints the runner version
 --commit   Prints the runner commit
 --check    Check the runner's network connectivity with GitHub server

Config Options:
 --unattended           Disable interactive prompts for missing arguments. Defaults will be used for missing options
 --url string           Repository to add the runner to. Required if unattended
 --token string         Registration token. Required if unattended
 --name string          Name of the runner to configure (default hostname on Linux - from C# Environment.MachineName)
 --runnergroup string   Name of the runner group to add this runner to (defaults to the default runner group)
 --labels string        Custom labels that will be added to the runner. This option is mandatory if --no-default-labels is used.
 --no-default-labels    Disables adding the default labels: e.g. 'self-hosted,Linux,X64'
 --local                Removes the runner config files from your local machine. Used as an option to the remove command
 --work string          Relative runner work directory (default _work)
 --replace              Replace any existing runner with the same name (default false)
 --pat                  GitHub personal access token with repo scope. Used for checking network connectivity when executing `./run.sh --check`
 --disableupdate        Disable self-hosted runner automatic update to the latest released version`
 --ephemeral            Configure the runner to only take one job and then let the service un-configure the runner after the job finishes (default false);

Examples:
 Check GitHub server network connectivity:
  ./run.sh --check --url <url> --pat <pat>

 Configure a runner non-interactively:
  ./config.sh --unattended --url <url> --token <token>

 Configure a runner non-interactively, replacing any existing runner with the same name:
  ./config.sh --unattended --url <url> --token <token> --replace [--name <name>]

 Configure a runner non-interactively with three extra labels:
  ./config.sh --unattended --url <url> --token <token> --labels L1,L2,L3;

Technical Notes

This new release:

  • uses a new GitHub Action to build and deploy the runner on Heroku using the sources endpoint API. This action allows you to deploy code living on GitHub repositories, even private, to apps running on Heroku without requiring the Heroku GitHub integration
  • uses ephemeral containers to allow autoscaling and hardening of self-hosted runners. Ephemeral runners are short-lived containers that are executed only once for a single job, providing isolated environments to reduce the risk of data leakage
  • logs the self-runner name to manage it from the GitHub dashboard
  • reduces the Docker image footprint and it's possible to run it as one-off dyno (CMD vs. ENTRYPOINT)
  • includes all the recent GitHub self-hosted runners features and streamlines the configuration and setup
  • integrates the Heroku Button to install the runner in one-click
  • supports fine-grained GitHub tokens

Credits

Credits to the owner of the original project that inspired this new updated version.