Deploy Cloud Run from GitHub with Cloud Build Substitutions – A Practical Guide
Learn how to set up a Cloud Build trigger that pulls code from GitHub, builds a container, and deploys to Cloud Run using substitution variables for environment‑specific settings. Includes a full example, permission checklist, and common pitfalls.
09 Aug 2025, 08:09 UTC

Problem: Deploying the Same Cloud Run Service to Multiple Environments
When a team uses a single cloudbuild.yaml to build a container image and deploy it to Cloud Run, the deployment step must change for each environment: the service name, region, image tag, and environment variables differ. Hard‑coding these values in the build config forces code changes for every environment, which is error‑prone and defeats the purpose of CI/CD automation.
Quick Answer
Use Cloud Build substitution variables in a GitHub trigger. Pass environment‑specific values (e.g., $ENV, $IMAGE_TAG, $SERVICE_NAME) to the trigger, and reference them in cloudbuild.yaml. The same build config can deploy to dev, staging, or prod without modification.
How Cloud Build Substitutions Work
Cloud Build substitutions are key/value pairs that replace placeholders in the build config or commands. They are defined at trigger creation or in cloudbuild.yaml under the substitutions field. During a build, Cloud Build expands any string that matches $KEY with the corresponding value. Substitutions are case‑sensitive, cannot start with a number, and each value is limited to 128 bytes. The total payload of the trigger (including substitutions) must be ≤1 MiB.
Example Configuration
cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', '$IMAGE_URL', '.' ]
- name: 'gcr.io/cloud-builders/docker'
args: [ 'push', '$IMAGE_URL' ]
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args: [
'run', 'deploy', '$SERVICE_NAME',
'--image', '$IMAGE_URL',
'--platform', 'managed',
'--region', '$REGION',
'--allow-unauthenticated',
'--set-env-vars', 'ENV=$ENV',
'--quiet'
]
substitutions:
_DEFAULT_REGION: 'us-central1'
timeout: 1200s
options:
logging: CLOUD_LOGGING_ONLY
The underscore prefix (_DEFAULT_REGION) is a convention for default values that can be overridden by trigger substitutions. The build uses the environment variable ENV to set an env var inside the Cloud Run container.
Trigger Settings on the Cloud Console
- Navigate to Cloud Build > Triggers and click CREATE TRIGGER.
- Choose GitHub as the source and select the repository.
- Set the event type to Push to a branch (e.g.,
main). - Under Substitutions, add the following key/value pairs for the dev environment:
ENV=devIMAGE_TAG=latestSERVICE_NAME=myapp-devREGION=us-central1IMAGE_URL=gcr.io/$PROJECT_ID/myapp:$IMAGE_TAG
- Click CREATE.
To deploy to staging or prod, duplicate the trigger and change the substitution values accordingly. No changes to cloudbuild.yaml are required.
Permissions & Setup Checklist
| Component | Required Role |
|---|---|
Cloud Build Service Account ([contact removed]) | roles/run.admin, roles/artifactregistry.admin, roles/storage.admin (if using GCS) |
| GitHub App | Read access to the repository; source.repos.get on the connected GitHub project |
Missing the roles/run.admin role will cause the gcloud run deploy step to fail with a permissions error. Verify the service account permissions using gcloud projects get-iam-policy $PROJECT_ID.
Common Mistakes to Avoid
- Undefined Substitutions: If a substitution is referenced but not supplied, Cloud Build substitutes an empty string. This can silently deploy to the wrong region or service name.
- Hard‑coded Values: Embedding region or image tag directly in
cloudbuild.yamldefeats the substitution mechanism. Keep all environment‑specific data in the trigger. - Exceeding Size Limits: Substitution values >128 bytes or a trigger payload >1 MiB will cause the build to fail. Keep values concise.
- Using the wrong case in substitution keys (e.g.,
$envvs$ENV) results in an empty string.
Verification Steps
- After the build completes, run:
gcloud run services describe $SERVICE_NAME \ --region $REGION \ --format='value(spec.template.containers[0].env)' - Confirm the output includes
ENV=dev(orstaging/prodas appropriate). - Test the service URL:
https://$SERVICE_NAME-.$REGION.run.appand verify the application reads the expected environment variable.
Limitations
- Substitutions cannot be nested; you cannot reference one substitution inside another.
- The
gcloud run deploycommand does not support--allow-unauthenticatedfor all services; adjust as needed. - If the image tag changes frequently, consider using a digest (
@sha256:...) to avoid cache invalidation issues.
Practical Takeaway
By configuring Cloud Build triggers with substitutions, you keep a single, clean cloudbuild.yaml that is agnostic to environment. This reduces maintenance overhead, eliminates hard‑coded values, and ensures consistent, reproducible deployments across dev, staging, and production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.