GCP for DevOps — Cloud Build

3 minute read

Cloud Build

Cloud Build is Google Cloud’s serverless CI/CD platform. It executes builds as a series of steps defined in cloudbuild.yaml, running each step in a Docker container.

How Cloud Build Works

┌──────────────────────────────────────────────┐
│             Cloud Build Execution             │
├──────────────────────────────────────────────┤
│                                               │
│  cloudbuild.yaml                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │ Step 1   ├──► Step 2   ├──► Step 3   │   │
│  │ (build)  │  │ (test)   │  │ (push)   │   │
│  └──────────┘  └──────────┘  └──────────┘   │
│       │              │             │          │
│       ▼              ▼             ▼          │
│  /workspace (shared volume across all steps)  │
│                                               │
│  Built-in builders:                           │
│  gcr.io/cloud-builders/docker                 │
│  gcr.io/cloud-builders/gcloud                 │
│  gcr.io/cloud-builders/kubectl                │
│  gcr.io/cloud-builders/npm                    │
│  gcr.io/cloud-builders/go                     │
└──────────────────────────────────────────────┘

Basic cloudbuild.yaml

# cloudbuild.yaml — Build, test, and push a Docker image
steps:
  # Step 1: Build the Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA', '.']

  # Step 2: Run tests
  - name: 'gcr.io/cloud-builders/docker'
    args: ['run', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA', 'npm', 'test']

  # Step 3: Push to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA']

images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'

options:
  logging: CLOUD_LOGGING_ONLY

Submit a Build Manually

# Submit from current directory
$ gcloud builds submit --config=cloudbuild.yaml .

# Submit with substitution variables
$ gcloud builds submit --config=cloudbuild.yaml \
    --substitutions=_ENV=staging,_VERSION=1.2.3 .

# Quick build — Dockerfile only (no cloudbuild.yaml needed)
$ gcloud builds submit --tag us-central1-docker.pkg.dev/my-project/my-repo/my-app:latest .

Built-in Substitution Variables

Variable Description
$PROJECT_ID GCP project ID
$BUILD_ID Unique build ID
$COMMIT_SHA Full Git commit SHA
$SHORT_SHA First 7 characters of commit SHA
$BRANCH_NAME Git branch name
$TAG_NAME Git tag name
$REPO_NAME Repository name
$_CUSTOM_VAR User-defined substitution (prefix _)

Multi-Step Pipeline Example

steps:
  # Install dependencies
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['ci']

  # Lint
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['run', 'lint']

  # Unit tests
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['test']

  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:latest'
      - '.'

  # Push image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '--all-tags', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app']

  # Deploy to Cloud Run
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'my-app'
      - '--image=us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
      - '--region=us-central1'
      - '--platform=managed'

images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:latest'

timeout: '1200s'

options:
  machineType: 'E2_HIGHCPU_8'

Parallel Steps

steps:
  - name: 'node:18'
    entrypoint: 'npm'
    args: ['ci']
    id: 'install'

  - name: 'node:18'
    entrypoint: 'npm'
    args: ['run', 'lint']
    id: 'lint'
    waitFor: ['install']

  - name: 'node:18'
    entrypoint: 'npm'
    args: ['test']
    id: 'test'
    waitFor: ['install']

  # lint and test run in parallel after install
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'my-app', '.']
    id: 'build'
    waitFor: ['lint', 'test']

Saving Artifacts

steps:
  - name: 'gcr.io/cloud-builders/go'
    args: ['build', '-o', 'app', '.']

artifacts:
  objects:
    location: 'gs://my-build-artifacts/builds/$BUILD_ID/'
    paths: ['app']

Build Configuration Options

Option Description
machineType E2_MEDIUM, E2_HIGHCPU_8, E2_HIGHCPU_32
diskSizeGb Disk size for build (default 100 GB)
timeout Max build duration (default 10 min, max 24 hr)
logging CLOUD_LOGGING_ONLY, GCS_ONLY, NONE
pool Private pool for VPC access

Service Account Permissions

Cloud Build uses a default service account: PROJECT_NUMBER@cloudbuild.gserviceaccount.com

# Grant Artifact Registry writer
$ gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:123456789@cloudbuild.gserviceaccount.com" \
    --role="roles/artifactregistry.writer"

# Grant Cloud Run deployer
$ gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:123456789@cloudbuild.gserviceaccount.com" \
    --role="roles/run.admin"

Common Mistakes

Mistake Why It’s Wrong Fix
No waitFor on independent steps Steps run sequentially by default Use id and waitFor for parallelism
Hardcoding project ID Breaks across projects Use $PROJECT_ID substitution
Default machine type for large builds Slow builds Use E2_HIGHCPU_8 or higher
Missing service account roles Build fails on push/deploy Grant required IAM roles
No timeout set Builds can run for 24 hours Set explicit timeout

Quick Reference

Command Description
gcloud builds submit --config=FILE . Submit a build
gcloud builds submit --tag TAG . Quick Docker build
gcloud builds list List recent builds
gcloud builds log BUILD_ID View build logs
gcloud builds cancel BUILD_ID Cancel a build

Next → Build Triggers

Updated: