By the end of this tutorial, you'll have GitHub Actions self-hosted runners running on ephemeral Google Cloud VMs. Every job gets its own fresh VM that boots in ~25 seconds, runs your workflow, and self-destructs — giving you full ubuntu-latest compatibility at a fraction of the cost of GitHub-hosted runners.
- A brand new GCP project with billing enabled — if you just created it, reload this tutorial to see it in the project picker below
- A GitHub account with a repository to test against
Click Next to begin.
Select your new project above, then set the project ID:
export PROJECT_ID=<walkthrough-project-id/>
gcloud config set project $PROJECT_IDsudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraformNavigate to the Terraform directory, create a GCS bucket for Terraform state, and initialize:
cd terraform
gcloud storage buckets create gs://${PROJECT_ID}-gcrunner-tfstate --location=us-central1
terraform init -backend-config="bucket=${PROJECT_ID}-gcrunner-tfstate" -backend-config="prefix=gcrunner"Set the required Terraform variables:
export TF_VAR_project_id=$PROJECT_ID
export TF_VAR_region=us-central1
export TF_VAR_gcrunner_version=v0.2.0Review and deploy:
terraform plan && terraform applyType yes when prompted.
Note: If you see an error like
Error 403: Identity and Access Management (IAM) API has not been used in project ... before or it is disabled, just re-runterraform apply— this commonly happens with the IAM and Cloud Tasks APIs on new projects and resolves itself once enablement propagates.
Terraform will create:
| Resource | Purpose |
|---|---|
| Cloud Run service | Webhook receiver and VM orchestrator |
| Service accounts | Minimal IAM for function and runner VMs |
| Artifact Registry | Remote repository for the gcrunner container image |
| Secret Manager secrets | GitHub App credentials storage |
| GCS bucket (cache) | Build cache storage |
| GCS bucket (tfstate) | Terraform remote state |
Get your setup URL and open it in your browser:
terraform output -raw setup_url && echo- Click Create GitHub App
- Name the app and select where to install it
- Select which repositories should have access to gcrunner
- Complete the installation
The credentials (app ID, private key, webhook secret) are automatically stored in Secret Manager.
In any repository where you installed the GitHub App, update your workflow to use gcrunner:
name: CI
on: [push, pull_request]
jobs:
build:
# This single line is all the configuration you need
runs-on: gcrunner=${{ github.run_id }}
steps:
- uses: actions/checkout@v4
- run: echo "Running on gcrunner!"All runner configuration lives in the runs-on field using simple labels:
# Specify machine type
runs-on: gcrunner=${{ github.run_id }}/machine=n2-standard-4
# Bigger machine, more disk
runs-on: gcrunner=${{ github.run_id }}/machine=c3-standard-8/disk=200gb
# No spot for production deploys
runs-on: gcrunner=${{ github.run_id }}/spot=falseAvailable labels:
| Label | Default | Description |
|---|---|---|
machine |
n2d-standard-2 |
GCE machine type |
spot |
true |
Use Spot VMs (60-91% cheaper), auto-fallback to on-demand |
disk |
75gb |
Boot disk size |
disk-type |
pd-ssd |
Boot disk type |
image |
ubuntu24-full-x64 |
Runner image family |
Trigger a workflow in one of your configured repositories, then check the logs:
gcloud run services logs read gcrunner-webhook --region=$TF_VAR_region --limit=20List active runner VMs:
gcloud compute instances list --filter="name~gcrunner"You've deployed gcrunner. Check gcrunner.com for full documentation.