GCP for DevOps — Container CI/CD

3 minute read

Container CI/CD

This page ties together Cloud Build, Artifact Registry, and Cloud Deploy into a complete container CI/CD pipeline for GKE and Cloud Run.

End-to-End Pipeline

┌──────────────────────────────────────────────────────────┐
│            Container CI/CD Pipeline                       │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  1. Push Code      2. Cloud Build       3. Artifact Reg  │
│  ┌──────┐         ┌────────────┐       ┌────────────┐   │
│  │ Git  ├────────►│ Build      ├──────►│ Docker     │   │
│  │ Push │  trigger│ Test       │  push │ Image      │   │
│  └──────┘         │ Scan       │       └─────┬──────┘   │
│                    └────────────┘             │           │
│                                               │           │
│  4. Cloud Deploy    5. GKE / Cloud Run        │           │
│  ┌────────────┐    ┌────────────┐             │           │
│  │ Release    │◄───┤ Deploy     │◄────────────┘           │
│  │ Promote    │    │ Verify     │                         │
│  │ Approve    │    │ Monitor    │                         │
│  └────────────┘    └────────────┘                         │
└──────────────────────────────────────────────────────────┘

Complete cloudbuild.yaml for GKE

steps:
  # Run unit tests
  - name: 'golang:1.22'
    entrypoint: 'go'
    args: ['test', './...']
    id: 'test'

  # Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api:$SHORT_SHA'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api:latest'
      - '--cache-from'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api:latest'
      - '.'
    id: 'build'
    waitFor: ['test']

  # Push to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '--all-tags', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api']
    id: 'push'
    waitFor: ['build']

  # Create Cloud Deploy release
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'deploy'
      - 'releases'
      - 'create'
      - 'release-$SHORT_SHA'
      - '--delivery-pipeline=api-pipeline'
      - '--region=us-central1'
      - '--images=api=us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api:$SHORT_SHA'
    id: 'release'
    waitFor: ['push']

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

options:
  machineType: 'E2_HIGHCPU_8'

Complete cloudbuild.yaml for Cloud Run

steps:
  # Build
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/web:$SHORT_SHA'
      - '.'
    id: 'build'

  # Push
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/web:$SHORT_SHA']
    id: 'push'
    waitFor: ['build']

  # Deploy to Cloud Run
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'web'
      - '--image=us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/web:$SHORT_SHA'
      - '--region=us-central1'
      - '--platform=managed'
      - '--min-instances=1'
      - '--max-instances=50'
    waitFor: ['push']

Binary Authorization

Enforce that only signed, verified images are deployed to GKE.

# Enable Binary Authorization
$ gcloud services enable binaryauthorization.googleapis.com

# Create an attestor
$ gcloud container binauthz attestors create build-attestor \
    --attestation-authority-note=build-note \
    --attestation-authority-note-project=my-project

# Set the default policy to require attestation
$ gcloud container binauthz policy export > policy.yaml
# policy.yaml — require attestation
admissionWhitelistPatterns:
  - namePattern: "gcr.io/google_containers/*"
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
  requireAttestationsBy:
    - projects/my-project/attestors/build-attestor
$ gcloud container binauthz policy import policy.yaml

Vulnerability Scanning in CI/CD

# Add scanning step to cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', '$_IMAGE', '.']

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '$_IMAGE']

  # Wait for scan results
  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        # Wait for vulnerability scan to complete
        sleep 30
        VULNS=$(gcloud artifacts docker images describe $_IMAGE \
          --show-package-vulnerability \
          --format='value(package_vulnerability_summary.severity_count)' 2>/dev/null)
        echo "Vulnerabilities: $VULNS"
        # Fail build if critical vulnerabilities found
        CRITICAL=$(echo "$VULNS" | grep -o 'CRITICAL:[0-9]*' | cut -d: -f2)
        if [ "$CRITICAL" -gt "0" ] 2>/dev/null; then
          echo "CRITICAL vulnerabilities found! Failing build."
          exit 1
        fi

substitutions:
  _IMAGE: 'us-central1-docker.pkg.dev/${PROJECT_ID}/my-repo/api:${SHORT_SHA}'

Multi-Architecture Builds

# Build for both amd64 and arm64
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['buildx', 'create', '--use']

  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'buildx'
      - 'build'
      - '--platform=linux/amd64,linux/arm64'
      - '-t'
      - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/api:$SHORT_SHA'
      - '--push'
      - '.'

Common Mistakes

Mistake Why It’s Wrong Fix
No Docker layer caching Slow builds Use --cache-from with latest tag
Skipping vulnerability scan Security risk Add scanning step in pipeline
No Binary Authorization Untrusted images deployed Enforce attestation policy
Building in production cluster Resource contention Use Cloud Build (off-cluster)
No image tag pinning Inconsistent deploys Use $SHORT_SHA, never just latest

Quick Reference

Pattern Tool
Build container Cloud Build + cloudbuild.yaml
Store image Artifact Registry
Scan image Container Analysis (automatic)
Deploy to GKE Cloud Deploy or kubectl in Cloud Build
Deploy to Cloud Run gcloud run deploy in Cloud Build
Enforce trusted images Binary Authorization

Next → Container Exercises

Updated: