DevOps: CI/CD Pipeline with Cloud Build
Scenario
You want code pushes to automatically build a Docker container and deploy it to serverless infrastructure. Instead of using GitHub Actions or Jenkins, you will use Google's native Cloud Build. You need to create a Build Trigger that listens to a GitHub repository branch, executes a `cloudbuild.yaml` file to build the Docker image, pushes it to Artifact Registry, and deploys it to Cloud Run.
- Provision a google_artifact_registry_repository to host the built images.
- Provision a dedicated google_service_account for Cloud Build to use.
- Grant this service account roles/run.admin, roles/iam.serviceAccountUser, and Artifact Registry Writer roles.
- Create a google_cloudbuild_trigger that points to a specific GitHub repository branch (assumes the GCP-GitHub app integration is pre-configured).
Additional Context
GitHub Integration: For `google_cloudbuild_trigger` to natively attach to a GitHub repository, a GCP Administrator must manually install the "Google Cloud Build" GitHub App into their GitHub organization out-of-band via the Cloud Console once. Terraform cannot do the initial OAuth handshake automatically.
Service Accounts: By default, Cloud Build uses the legacy Cloud Build Service Account `[Project_Number]@cloudbuild.gserviceaccount.com`. Security best practice mandates that you deploy a dedicated custom service account and attach it to the trigger via `service_account = ...` to enforce Least Privilege.
variables.tf
variable "project_id" { type = string }
variable "github_owner" { type = string; default = "my-github-username" }
variable "github_repo" { type = string; default = "my-app-repo" }main.tf
provider "google" {
project = var.project_id
region = "us-central1"
}
# 1. Image Registry
resource "google_artifact_registry_repository" "docker_repo" {
location = "us-central1"
repository_id = "cicd-app-repo"
format = "DOCKER"
}
# 2. Custom Service Account for the Pipeline
resource "google_service_account" "builder_sa" {
account_id = "cloud-build-runner"
display_name = "Cloud Build Execution Account"
}
# 3. IAM Bindings for the Builder SA
# Build needs to push images
resource "google_project_iam_member" "artifact_writer" {
project = var.project_id
role = "roles/artifactregistry.writer"
member = "serviceAccount:${google_service_account.builder_sa.email}"
}
# Build needs to deploy to Cloud Run
resource "google_project_iam_member" "run_admin" {
project = var.project_id
role = "roles/run.admin"
member = "serviceAccount:${google_service_account.builder_sa.email}"
}
# Build needs permission to act as the Cloud Run runtime Service Account
resource "google_project_iam_member" "sa_user" {
project = var.project_id
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.builder_sa.email}"
}
# 4. The Cloud Build Trigger
resource "google_cloudbuild_trigger" "push_to_main" {
name = "deploy-main-branch"
description = "Builds and deploys container to Cloud Run on push to main"
# Uses the connected GitHub repository (requires prior manual connection in Console)
github {
owner = var.github_owner
name = var.github_repo
push {
branch = "^main$"
}
}
# Use the custom Least-Privilege IAM identity for the build
service_account = google_service_account.builder_sa.id
# Look for this file in the root of the GitHub repository
filename = "cloudbuild.yaml"
# Define substitution variables that cloudbuild.yaml will ingest
substitutions = {
_REGION = "us-central1"
_REPO_NAME = google_artifact_registry_repository.docker_repo.name
_APP_NAME = "my-serverless-app"
}
}Sample cloudbuild.yaml (stored in GitHub)
steps:
# 1. Build the container image
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', '${_REGION}-docker.pkg.dev/${PROJECT_ID}/${_REPO_NAME}/${_APP_NAME}:${SHORT_SHA}', '.']
# 2. Push the image to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '${_REGION}-docker.pkg.dev/${PROJECT_ID}/${_REPO_NAME}/${_APP_NAME}:${SHORT_SHA}']
# 3. Deploy the container to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- '${_APP_NAME}'
- '--image'
- '${_REGION}-docker.pkg.dev/${PROJECT_ID}/${_REPO_NAME}/${_APP_NAME}:${SHORT_SHA}'
- '--region'
- '${_REGION}'
