Google Cloud
intermediate
30-40 minutes

Cloud Build CI/CD

Automate builds and deployments with Cloud Build - Google's serverless CI/CD platform.

What you'll accomplish

Automated build pipeline that deploys on every push to your repository

Prerequisites

  • GCP project with billing enabled
  • gcloud CLI configured
  • GitHub or Cloud Source Repository

What is Cloud Build?

Cloud Build is a serverless CI/CD platform that executes your builds on Google Cloud infrastructure.

How It Works

Code Push → Trigger → Cloud Build → Deploy
    │           │           │           │
    └── Git ────┴── Config ─┴── Steps ──┴── Cloud Run/GKE/etc

Key Features

  • Serverless - No servers to manage
  • Pay per use - First 120 build-minutes/day free
  • Fast - Parallel step execution
  • Flexible - Any Docker image as build step
  • Integrated - Native GCP service integration

Cloud Build vs Other CI/CD

FeatureCloud BuildGitHub ActionsJenkins
HostingServerlessServerlessSelf-hosted
GCP integrationNativeVia actionsVia plugins
Free tier120 min/day2000 min/monthN/A
Configurationcloudbuild.yamlworkflow.yamlJenkinsfile

Step 1: Enable Cloud Build API

bash
gcloud services enable cloudbuild.googleapis.com

Step 2: Connect Your Repository

Option A: GitHub

  1. Go to Cloud Build → Triggers → Connect Repository
  2. Select GitHub (Cloud Build GitHub App)
  3. Authenticate with GitHub
  4. Select your repository
  5. Accept the connection

Option B: Cloud Source Repositories

bash
# Create a Cloud Source Repository
gcloud source repos create my-repo

# Clone the repository
gcloud source repos clone my-repo

# Or mirror from GitHub
# Go to Cloud Source Repositories → Add repository → Connect external repository

Option C: GitLab or Bitbucket

Use the Cloud Build integration available in:

  • Cloud Build → Triggers → Connect Repository
  • Select your provider and authenticate

Step 3: Create Build Configuration

Basic cloudbuild.yaml

Create cloudbuild.yaml in your repository root:

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

  # Run tests
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['test']

  # Build the application
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['run', 'build']

Build and Push Docker Image

yaml
steps:
  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:latest'
      - '.'

# Push images to Artifact Registry
images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:latest'

Deploy to Cloud Run

yaml
steps:
  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
      - '.'

  # Push to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'push'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'

  # Deploy to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'myapp'
      - '--image'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
      - '--region'
      - 'us-central1'
      - '--platform'
      - 'managed'
      - '--allow-unauthenticated'

images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'

Step 4: Create a Build Trigger

Via Console

  1. Go to Cloud Build → Triggers
  2. Click Create Trigger
  3. Configure:
    • Name: deploy-on-push
    • Event: Push to a branch
    • Source: Your connected repository
    • Branch: ^main$ (regex)
    • Configuration: Cloud Build configuration file
    • Location: /cloudbuild.yaml

Via CLI

bash
gcloud builds triggers create github \
  --repo-name=my-repo \
  --repo-owner=my-github-user \
  --branch-pattern="^main$" \
  --build-config=cloudbuild.yaml \
  --name=deploy-on-push

Trigger Types

TypeWhen It Runs
Push to branchOn push matching branch pattern
Push new tagOn new tag matching pattern
Pull requestOn PR open/update (preview builds)
ManualManual trigger only
WebhookExternal webhook call

Step 5: Grant Permissions

Cloud Build needs permissions to deploy:

bash
# Get the Cloud Build service account
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
CLOUD_BUILD_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com"

# Grant Cloud Run Admin
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/run.admin"

# Grant Service Account User (to act as the runtime service account)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/iam.serviceAccountUser"

# Grant Artifact Registry Writer
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/artifactregistry.writer"

Step 6: Run and Monitor Builds

Manual Build

bash
# Submit a build manually
gcloud builds submit --config=cloudbuild.yaml .

# Submit with substitutions
gcloud builds submit \
  --config=cloudbuild.yaml \
  --substitutions=_ENV=production,_VERSION=1.0.0 \
  .

View Build History

bash
# List recent builds
gcloud builds list --limit=10

# View specific build
gcloud builds describe BUILD_ID

# Stream build logs
gcloud builds log BUILD_ID --stream

View in Console

  1. Go to Cloud Build → History
  2. Click on a build to see:
    • Step-by-step execution
    • Logs for each step
    • Artifacts produced
    • Timing information

Advanced Configuration

Substitution Variables

Built-in variables:

VariableDescription
$PROJECT_IDGCP project ID
$BUILD_IDUnique build ID
$COMMIT_SHAFull commit SHA
$SHORT_SHAFirst 7 characters of commit
$REPO_NAMERepository name
$BRANCH_NAMEBranch name
$TAG_NAMETag name (if triggered by tag)
$REVISION_IDCommit revision

Custom substitutions:

yaml
substitutions:
  _ENV: staging
  _REGION: us-central1

steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    args:
      - 'gcloud'
      - 'run'
      - 'deploy'
      - 'myapp-${_ENV}'
      - '--region=${_REGION}'

Parallel Steps

yaml
steps:
  # These run in parallel
  - name: 'node:20'
    id: 'lint'
    entrypoint: 'npm'
    args: ['run', 'lint']

  - name: 'node:20'
    id: 'test'
    entrypoint: 'npm'
    args: ['test']

  # This waits for both
  - name: 'node:20'
    id: 'build'
    entrypoint: 'npm'
    args: ['run', 'build']
    waitFor: ['lint', 'test']

Environment Variables

yaml
steps:
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['run', 'build']
    env:
      - 'NODE_ENV=production'
      - 'API_URL=https://api.example.com'

Secrets from Secret Manager

yaml
steps:
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['run', 'deploy']
    secretEnv: ['API_KEY', 'DATABASE_URL']

availableSecrets:
  secretManager:
    - versionName: 'projects/$PROJECT_ID/secrets/API_KEY/versions/latest'
      env: 'API_KEY'
    - versionName: 'projects/$PROJECT_ID/secrets/DATABASE_URL/versions/latest'
      env: 'DATABASE_URL'

Grant access to secrets:

bash
gcloud secrets add-iam-policy-binding API_KEY \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/secretmanager.secretAccessor"

Build Timeout

yaml
timeout: '1200s'  # 20 minutes

steps:
  - name: 'node:20'
    timeout: '300s'  # 5 minutes for this step
    entrypoint: 'npm'
    args: ['test']

Machine Type

yaml
options:
  machineType: 'E2_HIGHCPU_8'  # Faster builds
  diskSizeGb: 100              # More disk space

steps:
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['run', 'build']

Available machine types:

  • E2_MEDIUM (default)
  • E2_HIGHCPU_8
  • E2_HIGHCPU_32

Complete Example Configurations

Next.js to Cloud Run

yaml
# cloudbuild.yaml
steps:
  # Install dependencies
  - name: 'node:20'
    entrypoint: 'npm'
    args: ['ci']

  # Run linting
  - name: 'node:20'
    id: 'lint'
    entrypoint: 'npm'
    args: ['run', 'lint']

  # Run tests
  - name: 'node:20'
    id: 'test'
    entrypoint: 'npm'
    args: ['test']
    waitFor: ['-']  # Run in parallel with lint

  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/nextjs-app:$COMMIT_SHA'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/nextjs-app:latest'
      - '--build-arg'
      - 'NEXT_PUBLIC_API_URL=${_API_URL}'
      - '.'
    waitFor: ['lint', 'test']

  # Push image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'push'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/nextjs-app:$COMMIT_SHA'

  # Deploy to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'nextjs-app'
      - '--image'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/nextjs-app:$COMMIT_SHA'
      - '--region'
      - 'us-central1'
      - '--platform'
      - 'managed'
      - '--allow-unauthenticated'
      - '--memory'
      - '512Mi'
      - '--cpu'
      - '1'

substitutions:
  _API_URL: 'https://api.example.com'

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

options:
  machineType: 'E2_HIGHCPU_8'

Multi-Environment Deployment

yaml
# cloudbuild-staging.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
      - '.'

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA']

  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'myapp-staging'
      - '--image'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$COMMIT_SHA'
      - '--region'
      - 'us-central1'
      - '--set-env-vars'
      - 'NODE_ENV=staging'
yaml
# cloudbuild-production.yaml
steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'myapp-production'
      - '--image'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/docker-repo/myapp:$TAG_NAME'
      - '--region'
      - 'us-central1'
      - '--set-env-vars'
      - 'NODE_ENV=production'

Create triggers:

  • Staging: Push to develop branch → cloudbuild-staging.yaml
  • Production: Push tag v* → cloudbuild-production.yaml

Static Site to Cloud Storage

yaml
steps:
  # Install and build
  - name: 'node:20'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        npm ci
        npm run build

  # Deploy to Cloud Storage
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gsutil'
    args:
      - '-m'
      - 'rsync'
      - '-r'
      - '-d'
      - './dist'
      - 'gs://my-website-bucket'

  # Set cache headers
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: 'gsutil'
    args:
      - '-m'
      - 'setmeta'
      - '-h'
      - 'Cache-Control:public, max-age=31536000'
      - 'gs://my-website-bucket/**/*.js'

Build Triggers for Different Workflows

Feature Branch Previews

bash
gcloud builds triggers create github \
  --repo-name=my-repo \
  --repo-owner=my-user \
  --branch-pattern="^feature/.*$" \
  --build-config=cloudbuild-preview.yaml \
  --name=preview-build

Pull Request Checks

bash
gcloud builds triggers create github \
  --repo-name=my-repo \
  --repo-owner=my-user \
  --pull-request-pattern="^main$" \
  --build-config=cloudbuild-pr.yaml \
  --name=pr-check \
  --comment-control=COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY

Tag-Based Production Deploys

bash
gcloud builds triggers create github \
  --repo-name=my-repo \
  --repo-owner=my-user \
  --tag-pattern="^v[0-9]+\.[0-9]+\.[0-9]+$" \
  --build-config=cloudbuild-production.yaml \
  --name=production-deploy

Notifications

Slack Notifications

  1. Create a Cloud Function to send Slack messages
  2. Set up Pub/Sub topic for build notifications
  3. Subscribe the function to the topic
bash
# Create Pub/Sub topic
gcloud pubsub topics create cloud-builds

# Cloud Build automatically publishes to this topic

Email Notifications

  1. Go to Cloud Build → Settings
  2. Enable email notifications
  3. Configure recipients

Cost Optimization

Free Tier

  • 120 build-minutes per day (free)
  • E2_MEDIUM machine type

Reduce Build Time

  1. Use caching - Cache node_modules, build outputs
  2. Parallel steps - Run independent steps concurrently
  3. Smaller base images - Use Alpine or slim variants
  4. Multi-stage builds - Reduce final image size

Example with Caching

yaml
steps:
  # Restore cache
  - name: 'gcr.io/cloud-builders/gsutil'
    args:
      - '-m'
      - 'cp'
      - '-r'
      - 'gs://${PROJECT_ID}-build-cache/node_modules'
      - '.'
    allow_failure: true

  # Install only if cache miss
  - name: 'node:20'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        if [ ! -d "node_modules" ]; then
          npm ci
        fi

  # Save cache
  - name: 'gcr.io/cloud-builders/gsutil'
    args:
      - '-m'
      - 'cp'
      - '-r'
      - 'node_modules'
      - 'gs://${PROJECT_ID}-build-cache/'

Troubleshooting

Build Fails: Permission Denied

bash
# Check Cloud Build service account permissions
gcloud projects get-iam-policy $PROJECT_ID \
  --flatten="bindings[].members" \
  --filter="bindings.members:cloudbuild.gserviceaccount.com"

# Grant missing role
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/MISSING_ROLE"

Build Fails: Docker Push Denied

bash
# Grant Artifact Registry access
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/artifactregistry.writer"

Build Timeout

yaml
# Increase timeout (max 24 hours)
timeout: '3600s'

options:
  machineType: 'E2_HIGHCPU_8'  # Faster builds

View Detailed Logs

bash
# Stream logs for running build
gcloud builds log BUILD_ID --stream

# Download logs
gcloud builds log BUILD_ID > build.log
Note

Checkpoint: Create a cloudbuild.yaml file, connect your repository, and verify that pushing to your main branch triggers an automatic deployment.