{"id":18941515,"url":"https://github.com/mchmarny/grunner","last_synced_at":"2025-04-15T20:32:03.964Z","repository":{"id":176112803,"uuid":"654309701","full_name":"mchmarny/grunner","owner":"mchmarny","description":"Self-hosted GitHub Actions runner on GCP using GCE.","archived":false,"fork":false,"pushed_at":"2023-06-25T12:56:14.000Z","size":308,"stargazers_count":36,"open_issues_count":1,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-12T12:36:50.899Z","etag":null,"topics":["actions","gce","gcp","mig","runner","terraform","workflow"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mchmarny.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}},"created_at":"2023-06-15T21:05:39.000Z","updated_at":"2024-12-03T20:03:58.000Z","dependencies_parsed_at":"2023-07-18T12:15:31.277Z","dependency_job_id":null,"html_url":"https://github.com/mchmarny/grunner","commit_stats":null,"previous_names":["mchmarny/grunner"],"tags_count":9,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchmarny%2Fgrunner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchmarny%2Fgrunner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchmarny%2Fgrunner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mchmarny%2Fgrunner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mchmarny","download_url":"https://codeload.github.com/mchmarny/grunner/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249148043,"owners_count":21220469,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","gce","gcp","mig","runner","terraform","workflow"],"created_at":"2024-11-08T12:28:20.430Z","updated_at":"2025-04-15T20:32:03.945Z","avatar_url":"https://github.com/mchmarny.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# grunner\n\nSelf-hosted GitHub Actions runner on GCP using GCE.\n\n![](assets/overview.png)\n\nTerraform provisioning for: \n\n* Ephemeral VMs in Managed Instance Group (MIG)\n* Each VM instance processes maximum of one workflow job\n* Each runner provisioned with its own GiHub token\n* Configurable worker pool (size, machine type, CPU, memory, accelerators)\n* VPC and VPC Service Controls with User-defined IAM service account, roles\n* Customizable runner image with startup/shutdown script hooks\n\n## prerequisites\n\nSince you are interested in self-hosted runners on GCP, you probably already have GCP account and a project. If not, review [creating projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).\n\nYou will also need `gcloud`. You can find instructions on how to install it [here](https://cloud.google.com/sdk/docs/install). Mak sure to authenticate and set the default project:\n  \n```shell\ngcloud auth application-default login\ngcloud config set project $PROJECT_ID\n```\n\n## setup\n\nStart by forking this repo using this `Use this template` button at the top of this page:\n\n\u003e When prompted, make sure to use private repo. Forks of a public repository could potentially run dangerous code on your machine by creating a pull request that executes the code in a workflow. More on self-hosted runner security [here](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security).\n\nNext, clone your own repo locally and initialize it: \n\n\u003e This will update all repo specific names from the template to your new repo name. \n\n```shell\nscripts/repo-init\n```\n\nNext, create the custom GCE VM image:\n\n\u003e See [scripts/img-startup](scripts/img-startup) for the content of the script that's used to configure the image. Pretty minimal for this demo, you can customize to your needs. \n\n```shell\nscripts/img\n```\n\nThe final response will include the version of your new image that will be used to underpin the runner VMs looking something like this:\n\n```shell\nimage created: \u003cyour-project-id\u003e/grunner-\u003cversion\u003e\n```\n\nNext, create Terraform variables file: `deployments/terraform.tfvars`:\n\n\u003e Update as necessary. The Personal Access Tokens (PAT) is only needed to query the GitHub API to obtain registration token for each VM. You can find more about PATs [here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n\n```shell\nname    = \"grunner\"\nproject = \"\u003cyour-project-id\u003e\"\nregion  = \"us-west1\"\nrepo    = \"mchmarny/grunner\"\ntoken   = \"\u003cgithub-pat\u003e\"\nimage   = \"\u003cyour-project\u003e/\u003cyour-custom-image-name\u003e\"\n```\n\n\u003e For complete list of the variables you can define see [deployments/variables.tf](deployments/variables.tf).\n\nCreate GCS bucket to store Terraform state. Couple of things to keep in mind: \n\n* Bucket name has to be globally unique\n* The `project` flag value should match the values from `deployments/terraform.tfvars` \n* Bucket name must match the name in `deployments/backend.tf`. \n\n```shell\ngcloud storage buckets create \\\n    gs://grunner-terraform-state \\\n    --project $PROJECT_ID \\\n    --location us-west1\n```\n\nWhen done, navigate into the [./deployments](./deployments) directory, and initialize Terraform:\n\n```shell\ncd deployments\nterraform init\n```\n\n## deploy\n\nAssuming you completed the above `setup`, you can now `apply` the configuration to deploy your private GitHub runners:\n\n```shell\nterraform apply\n```\n\nCheck that at least one runner is registered by navigating to: https://github.com/$OWNER/$REPO/settings/actions/runners \n\n![](assets/runners.png)\n\n\u003e It may take up to 1 min after Terraform completed for all the runners to register in GitHub UI.\n\n## usage\n\nYour GitHub Actions workflows are same as with the managed runner. The only update is the `runs-on` value, which is not set to `self-hosted`\n\n```yaml\njobs:\n  demo:\n    name: Test runner\n    permissions:\n      contents: read\n    runs-on: self-hosted\n    ...\n```\n\n## slsa\n\nEven though the runner is different, you can still use the [SLSA Generator](https://github.com/slsa-framework/slsa-github-generator) as is. See the example workload [here](.github/workflows/slsa.yaml), and the resulting SLSA attestation [here](samples/cosign-att-payload.json).\n\n## debug\n\nIf VM starts, but you do not see the runners registered in GitHub settings, start by listing the instances: \n\n```shell\ngcloud compute instances list --filter \"tags.items=grunner\" --project $PROJECT_ID\n```\n\nThe response should look something like this: \n\n```shell\nNAME          ZONE        MACHINE_TYPE  INTERNAL_IP  EXTERNAL_IP  STATUS\ngrunner-5hdz  us-west1-b  e2-medium     10.138.0.55  *.*.*.*      RUNNING\ngrunner-98c6  us-west1-c  e2-medium     10.138.0.50  *.*.*.*      RUNNING\n...\n```\n\nPick one of the VMs and connect to it using SSH: \n\n```shell\ngcloud compute ssh grunner-5hdz \\\n  --tunnel-through-iap \\\n  --zone us-west1-b \\\n  --project $PROJECT_ID\n```\n\nCheck the output of the startup script:\n\n```shell\nsudo journalctl -u google-startup-scripts.service\n```\n\nThe last few lines should confirm that the runner service has started: \n\n```shell\n...\ngoogle_metadata_script_runner[1450]: Finished running startup scripts.\nsystemd[1]: google-startup-scripts.service: Deactivated successfully.\nsystemd[1]: Finished Google Compute Engine Startup Scripts.\n```\n\nIf not, you can re-run the script, to find the issue: \n\n```shell\nsudo google_metadata_script_runner startup\n```\n\n## cleanup\n\nTo destroy all resources created by this demo:\n\n\u003e Note, make sure to enter `yes` when prompted.\n\n```shell\nterraform destroy\n```\n\n## disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmchmarny%2Fgrunner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmchmarny%2Fgrunner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmchmarny%2Fgrunner/lists"}