Service Accounts for BYOC on GCP
BYOC on GCP uses two service accounts: one the cloud connection creates in your project, one you attach to runner VMs. Roles, setup order, and review points.
A GCP BYOC deployment uses two service accounts with different owners, and telling them apart answers most of the permission questions a review raises. The cloud connection creates one service account in your project that WarpBuild impersonates to create and terminate runner instances, and you create a second one that gets attached to the runner VMs so job steps reach Artifact Registry or Cloud Storage without a key in a GitHub secret.
This page covers what each identity is allowed to do, the enablement order that has to land before the connect flow succeeds, how to narrow the roles on the instance account, and the checks a platform team runs afterwards. The click path with screenshots is in the service account setup guide, and the account-level view of the deployment is on BYOC runners on GCP for GitHub Actions.
Overview
BYOC runs on AWS, GCP, and Azure. On GCP, runners are Compute Engine instances in your own project, so every identity involved lives in that project and is visible in its IAM policy.
| Connection service account | Instance service account | |
|---|---|---|
| Created by | The cloud connect flow, in your project | You, with gcloud or the console |
| Used by | The WarpBuild control plane, through impersonation | The runner VM, and therefore your workflow steps |
| Purpose | Build stacks, create and terminate runner instances | Authenticate job steps to Google APIs |
| Configured where | Once, when the project is connected | Per runner, in the Service Account field on the runner edit page |
| How many | One per cloud connection | As many as you want, one per workload shape |
| What a compromised build step reaches through it | Nothing; the control plane holds it | Exactly the roles you granted, and nothing else |
| Removed by | Deleting the cloud connection | Deleting the service account or clearing the field |
The split is what lets a deploy runner and a pull request runner hold different Google authority under one stack. Both run on the same VPC, the same bucket, and the same connection; only the attached identity differs.
Architecture
The connection side is an impersonation chain. WarpBuild's own service account calls the IAM Service Account Credentials API to mint short lived tokens for the service account that lives in your project, and those tokens are what sign the Compute Engine calls that create and terminate instances. No long-lived key is issued, which is why the credentials API has to be enabled before the connect flow starts, and why deleting the connection ends the access path.
The instance side never touches WarpBuild. The service account you select is attached to the VM at boot, the metadata server serves tokens for it, and gcloud, docker login against Artifact Registry, and the Google client libraries pick those tokens up on their own. Nothing has to be stored in a repository secret, and nothing has to be rotated on a schedule.
One binding joins the two. WarpBuild's runner-creating service account, shown as CREATOR_SA on the BYOC page in the dashboard, needs roles/iam.serviceAccountUser on the instance service account before it can pass that identity to a VM. Grant it with gcloud iam service-accounts add-iam-policy-binding, which writes the binding onto the service account resource itself. That distinction matters in review: the grant authorizes passing exactly one named identity, and every other service account in the project stays out of reach.
Configuration
Three prerequisites decide whether the connect flow succeeds, and each failure looks different in the console.
| Step | What it unlocks | What fails without it |
|---|---|---|
| Link a billing account to the project | Google bills compute, disk, storage, and network to your account | The project cannot hold the resources a stack creates |
| Enable the Identity and Access Management (IAM) API | Creation of the connection service account in your project | Connect fails before any resource exists |
| Enable the IAM Service Account Credentials API | Short lived tokens minted by impersonating that account | Impersonation fails, so no runner instance is ever created |
| Enable Compute Engine, Cloud Storage, Cloud Deployment Manager V2, and Cloud Resource Manager | Stack deployment, the cache bucket, and runner lifecycle | The stack deployment stops at the missing API |
| Grant the setup user Security Admin, Storage Admin, Deployment Manager Editor, Compute Admin | Creating the integration, bucket, deployment, and compute resources | The flow stops at whichever resource the missing role covers |
Enable every API against the project you plan to connect. The project dropdown in the GCP console is the usual cause of a failed connect, since the API gets enabled on a neighboring project. The full API table and the console links are in the GCP configuration guide.
Those four setup roles belong to a human operator. Runner instances never carry them, and they are exercised when the connection and the stack are created, and again when a pending connection or stack update is applied, so teams that keep standing project admin narrow usually hold them on a setup principal and re-grant when an update is waiting.
Creating the instance service account is three commands.
gcloud config set project "${PROJECT_ID}"
gcloud iam service-accounts create ci-runner \
--display-name="GitHub Actions runner identity"
export SA_EMAIL="ci-runner@${PROJECT_ID}.iam.gserviceaccount.com"
# CREATOR_SA is the runner-creating service account shown on the BYOC page
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
--member="serviceAccount:${CREATOR_SA}" \
--role="roles/iam.serviceAccountUser"Then grant the account whatever the jobs actually do. The setup guide uses admin roles as an illustration; production fleets should start narrower and widen only when a step fails.
| What the job does | Start with | Wider role to avoid by default |
|---|---|---|
| Pull images from Artifact Registry | roles/artifactregistry.reader | roles/artifactregistry.admin |
| Push images to Artifact Registry | roles/artifactregistry.writer | roles/artifactregistry.admin |
| Read build inputs from one bucket | roles/storage.objectViewer, granted on that bucket | roles/storage.admin on the project |
| Write test artifacts to one bucket | roles/storage.objectUser, granted on that bucket | roles/storage.admin on the project |
Bucket-scoped grants use gcloud storage buckets add-iam-policy-binding rather than gcloud projects add-iam-policy-binding, which keeps the identity out of every other bucket in the project. Google documents the full set in the predefined roles reference. Finish by selecting the account in the Service Account field on the runner configuration. The field is editable on an existing runner, so a fleet that was created before the identity existed is updated in place rather than rebuilt.
Operations
Verify the attachment on a live instance rather than trusting the form. Open Compute Engine in the GCP console, pick a running runner instance, and read Service account under API and identity management; it should match the account you created. The same check from a workflow step is one curl against the metadata server:
jobs:
identity:
runs-on: warp-custom-gcp-use-linux-8x
steps:
- run: |
curl -sH 'Metadata-Flavor: Google' \
'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email'Three review habits keep the setup honest over time. Diff the project IAM policy after every stack or connection update, since applying an update can change what the connection holds. Alert on changes to the roles/iam.serviceAccountUser binding on each instance service account, because that binding is what decides which identities can be attached to a VM. And split identities by workload before splitting them by team: a pull request runner that only reads images and a deploy runner that writes them are the two accounts most fleets end up with.
Costs sit on a separate line from permissions and do not change when you add another identity. WarpBuild charges $0.002 per minute per Linux BYOC runner, and Google bills your own project for the Compute Engine instances, the disks, the bucket, and the network at whatever rates your account already pays (WarpBuild pricing, checked 2026-08-13).
On GCP BYOC the Linux sets are the ones this service account is attached to; macOS and Windows jobs stay on hosted warp- labels in the same workflow file. For the compliance question that usually follows the IAM one: SOC 2 Type 2, with trust.warpbuild.com as the linked evidence.
FAQ
How many service accounts does BYOC on GCP use?
Two, with different owners. The cloud connection creates one in your project and WarpBuild impersonates it to create and terminate runner instances. You create the second one and select it on the runner configuration, and it is what your workflow steps authenticate as.
Do I need to put a service account key in a GitHub secret?
No. The instance service account is attached to the Compute Engine VM, so job steps get tokens from the instance metadata server and gcloud picks them up without a JSON key file anywhere in the repository.
Which roles does the person running the connect flow need?
Security Admin, Storage Admin, Deployment Manager Editor, and Compute Admin, granted on the project being connected. They cover creating the connection service account, the Cloud Storage bucket, the Deployment Manager deployment, and the compute resources, and they apply to the setup user rather than to the runners.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.