Developer Tools and DevOps

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands and product statuses on this page were checked against docs.cloud.google.com on 2026-10-01: gcloud CLI 586.x, Cloud Build and Cloud Deploy as documented there, Skaffold 2.x (skaffold/v4beta13 schema), the google-github-actions actions at v3, and the Terraform hashicorp/google provider v8. Developer-tool products are renamed, merged and closed regularly, so confirm availability in the linked pages before designing around any of them.

This page covers the tooling around Google Cloud rather than one runtime service: the IDE and browser environments used to write Bookshelf (catalog-api, orders-api, notifier), the local emulators used to test it, and the pipelines that carry a Git push to Cloud Run and GKE. Building and storing images (Artifact Registry, Cloud Build basics, Jib and buildpacks, the Container Registry shutdown) is in Container Registry and Builds; the Workload Identity Federation pool and provider setup is in Identity and Access; Terraform and Infrastructure Manager are in Infrastructure as Code. Pipeline mechanics that are not specific to Google Cloud (caching, matrix builds, environments) are in Docker CI/CD with GitHub Actions. The AWS and Azure counterparts are Developer Tools and DevOps and Developer Tools and DevOps.

Enable the APIs used here:

gcloud services enable workstations.googleapis.com clouddeploy.googleapis.com \
  cloudbuild.googleapis.com artifactregistry.googleapis.com run.googleapis.com \
  binaryauthorization.googleapis.com containeranalysis.googleapis.com \
  iamcredentials.googleapis.com sts.googleapis.com

Choosing a CI/CD approach

There are two broad options and they mix well: keep the pipeline in the system that already hosts the code (GitHub Actions, GitLab CI/CD, Jenkins) and let it call Google Cloud with short-lived credentials, or use Google’s own services (Cloud Build to build, Cloud Deploy to promote) that run inside the project.

flowchart LR Dev[Developer push] --> GH[GitHub or GitLab repository] GH -->|OIDC token| WIF[Workload Identity Federation] WIF --> SA[Service account access token] SA --> AR[(Artifact Registry image)] SA --> RUN[Cloud Run service] SA --> GKE[GKE kubectl apply] GH -->|2nd-gen connection trigger| CB[Cloud Build] CB --> AR CB --> CD[Cloud Deploy release] CD --> DEV[dev target] CD --> PROD[prod target after approval]

Use the external CI system when the team already lives in it, the build needs nothing from inside a private network, and you want one pipeline definition across clouds. Use Cloud Build when builds must reach private resources (private pools), when you want build provenance generated by the platform, or when the repository connection should be managed in the project. Add Cloud Deploy when promotion between environments, approvals and rollbacks should be tracked as a first-class resource instead of being scripted. Whichever you choose, never create a downloaded service-account JSON key for the pipeline: new organizations block key creation by default, and older guides that store a key as a CI secret are outdated.

Cloud Code and Gemini Code Assist

Cloud Code is a set of plug-ins for Visual Studio Code, the JetBrains IDEs (IntelliJ IDEA, GoLand, PyCharm and others), Cloud Shell Editor and Cloud Workstations. It adds a Google Cloud explorer, project and credential pickers, YAML snippets and schema validation for Kubernetes and Cloud Run files, and an inner development loop that builds with Skaffold, deploys to a local or remote cluster and streams logs. Cloud Code now bundles Gemini Code Assist, Google’s AI coding assistant (chat, code completion and code generation), which has a free tier for individuals and paid editions for organizations whose terms control data use; check the product page for the edition that fits your policy.

Common tasks:

Run on Kubernetes

Builds the image with the builder declared in skaffold.yaml (Docker, Jib or buildpacks), deploys the manifests to the current kubectl context, forwards ports and rebuilds on save.

Run on Cloud Run Emulator

Runs the service locally in a container that mimics Cloud Run (environment, port and service-account behaviour) and can attach a debugger.

Deploy to Cloud Run

Deploys the current folder from the IDE using the same engine as gcloud run deploy --source.

Explorer views

Browse Cloud Run services, GKE clusters, Secret Manager secrets and Cloud Storage buckets without leaving the editor.

Older guides describe Cloud Code for IntelliJ with Skaffold and Jib as the way to iterate on a Spring Boot app on GKE; that flow is still current, only the assistant features are new. Installation is a normal plug-in install from the IDE’s marketplace, followed by Sign in with Google and choosing a project; the IDE then uses Application Default Credentials as described in Getting Started with Google Cloud.

Skaffold for the inner loop

Skaffold is the open-source tool behind both Cloud Code’s loop and Cloud Deploy’s rendering. A minimal configuration for catalog-api builds with Jib and deploys plain Kubernetes manifests:

apiVersion: skaffold/v4beta13
kind: Config
metadata:
  name: catalog-api
build:
  artifacts:
    - image: catalog-api
      jib:
        project: com.example.bookshelf:catalog-api
manifests:
  rawYaml:
    - k8s/deployment.yaml
    - k8s/service.yaml
deploy:
  kubectl: {}

Run it from a terminal with skaffold dev --default-repo=us-central1-docker.pkg.dev/$PROJECT_ID/bookshelf and the schema version can be upgraded with skaffold fix. The same file is reused unchanged by Cloud Deploy later on this page. Jib and buildpacks are explained in Container Registry and Builds.

Cloud Shell and Cloud Workstations

Cloud Shell is the free, browser-based terminal with the gcloud CLI, Docker, Git and an editor based on Code OSS already installed. Its limits (50 hours per week, 5 GB of persistent home directory, home deleted after 120 days of inactivity) make it right for experiments and wrong for daily development; it is covered in Getting Started with Google Cloud.

Cloud Workstations is the step up: managed, per-developer development environments that run in your project and VPC. Each workstation is a VM with a persistent home disk and a container image that carries the tools. Developers reach it from a browser (Code OSS or a JetBrains IDE in the browser), from a local VS Code or JetBrains client, or over SSH through a TCP tunnel. Because the environment lives in the project, source code does not need to land on laptops, the network path to private databases is the VPC, and the base image can be standardized.

Three resource levels exist:

Workstation cluster

A regional control plane attached to a VPC network and subnet. It is the unit that incurs an hourly management fee while it exists.

Workstation configuration

A template: machine type, disk, idle and running timeouts, the container image (a Google predefined image such as Code OSS, a JetBrains image, or your own from Artifact Registry), network settings and who may create workstations from it.

Workstation

One developer’s environment created from a configuration. It stops after the idle timeout so that compute billing pauses while the persistent disk remains.

In the Google Cloud console

  1. Open Cloud Workstations (enable the API if prompted) and use the project picker to select the project.

  2. Choose Cluster management > Create to make a workstation cluster. Name it bookshelf-ws, pick region us-central1, and select the VPC network and subnet. Cluster creation takes several minutes.

  3. Open Workstation configurations > Create. Pick the cluster, a machine type such as e2-standard-4, the idle timeout, the persistent disk size and the container image, then create it.

  4. Open Workstations > Create workstation, select the configuration, and create. Use Start and then Launch to open the IDE in the browser; Stop ends compute billing.

  5. On the configuration, Permissions controls who may create workstations (roles/workstations.user on the workstation controls who may use one).

With the gcloud CLI

export REGION=us-central1

gcloud workstations clusters create bookshelf-ws \
  --region="$REGION" --network="projects/$PROJECT_ID/global/networks/default" \
  --subnetwork="projects/$PROJECT_ID/regions/$REGION/subnetworks/default"

gcloud workstations configs create bookshelf-config \
  --cluster=bookshelf-ws --region="$REGION" \
  --machine-type=e2-standard-4 --idle-timeout=1200 --pool-size=0 \
  --container-predefined-image=codeoss

gcloud workstations create bookshelf-dev \
  --cluster=bookshelf-ws --config=bookshelf-config --region="$REGION"

gcloud workstations start bookshelf-dev \
  --cluster=bookshelf-ws --config=bookshelf-config --region="$REGION"

To connect from a local IDE or terminal, open a tunnel to the workstation’s SSH port and connect through it:

gcloud workstations start-tcp-tunnel bookshelf-dev 22 \
  --cluster=bookshelf-ws --config=bookshelf-config --region="$REGION" \
  --local-host-port=localhost:2222
ssh -p 2222 user@localhost

A pool size above zero keeps that many started VMs ready so that workstations launch quickly, at the cost of paying for idle machines; keep it at zero until startup time hurts. A custom image is the way to preinstall JDK 25, Maven, the gcloud CLI and company certificates; build it with Cloud Build and push it to the bookshelf repository, then reference it with --container-custom-image.

A Cloud Build pipeline for Bookshelf

Cloud Build triggers, repository connections, the build service account and cloudbuild.yaml basics are in Container Registry and Builds. This section adds the step that most pipelines need next: deploying what was built. The following cloudbuild.yaml builds catalog-api, pushes it and deploys it to Cloud Run by digest:

steps:
  - id: build
    name: gcr.io/cloud-builders/docker
    args: ['build', '-t', '${_IMAGE}:$SHORT_SHA', '.']
  - id: push
    name: gcr.io/cloud-builders/docker
    args: ['push', '${_IMAGE}:$SHORT_SHA']
  - id: deploy
    name: gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
    entrypoint: gcloud
    args:
      - run
      - deploy
      - catalog-api
      - --image=${_IMAGE}:$SHORT_SHA
      - --region=${_REGION}
substitutions:
  _IMAGE: us-central1-docker.pkg.dev/my-project-id/bookshelf/catalog-api
  _REGION: us-central1
options:
  logging: CLOUD_LOGGING_ONLY

The build service account then needs roles/run.developer on the project and roles/iam.serviceAccountUser on the service account the Cloud Run service runs as. Grant them on the narrowest resource that works:

BUILDER="serviceAccount:ci-builder@$PROJECT_ID.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="$BUILDER" --role=roles/run.developer

gcloud iam service-accounts add-iam-policy-binding \
  "catalog-api@$PROJECT_ID.iam.gserviceaccount.com" \
  --member="$BUILDER" --role=roles/iam.serviceAccountUser

Deploying straight from a build is fine for a single environment. When several environments, approvals and rollbacks are involved, hand the image to Cloud Deploy instead of repeating gcloud run deploy in more steps.

Cloud Deploy

Cloud Deploy is a managed continuous delivery service. You describe a delivery pipeline (an ordered list of stages), the targets those stages deploy to (Cloud Run services, GKE clusters, GKE Enterprise or Anthos clusters), and a Skaffold configuration that renders and deploys the manifests. A CI system creates a release, a snapshot of the images and rendered manifests; Cloud Deploy then creates a rollout of that release to the first target and you promote it through the following ones, optionally requiring approval. Rollbacks and the history of what ran where are resources you can list, which is what makes it more than a script.

Strategies

Standard (replace), canary (shift traffic in percentages, with optional verification jobs), and blue-green variants on supported targets. For Cloud Run targets canary uses revision traffic splitting as described in Cloud Run Scaling, Traffic and Jobs; for GKE targets it uses Gateway API or Service-based traffic.

Execution identity

Each target declares the service account that renders and deploys; the default is the Compute Engine default service account, which is broader than needed. Use a dedicated account with roles/clouddeploy.jobRunner, the runtime role (for example roles/run.developer) and roles/iam.serviceAccountUser on the runtime identity.

Approvals

A target with requireApproval: true waits until someone with roles/clouddeploy.approver approves the rollout.

In the Google Cloud console

  1. Open Cloud Deploy (enable the API if prompted). The Delivery pipelines list shows each pipeline with its stages.

  2. Pipelines and targets are created from YAML, so the console is mostly for inspection: select bookshelf-pipeline, open a release, and watch each rollout and its jobs.

  3. On a rollout waiting for approval choose Approve or Reject; on a succeeded rollout choose Promote to the next target, or Rollback to redeploy the previous release.

  4. Targets lists each target and its execution service account.

With the gcloud CLI

Describe a two-stage pipeline (dev then prod) and its Cloud Run targets in one file:

# clouddeploy.yaml
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
  name: bookshelf-pipeline
serialPipeline:
  stages:
    - targetId: bookshelf-dev
      profiles: [dev]
    - targetId: bookshelf-prod
      profiles: [prod]
      strategy:
        canary:
          runtimeConfig:
            cloudRun:
              automaticTrafficControl: true
          canaryDeployment:
            percentages: [25, 50]
            verify: false
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: bookshelf-dev
run:
  location: projects/my-project-id/locations/us-central1
executionConfigs:
  - usages: [RENDER, DEPLOY]
    serviceAccount: deployer@my-project-id.iam.gserviceaccount.com
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: bookshelf-prod
requireApproval: true
run:
  location: projects/my-project-id/locations/us-central1
executionConfigs:
  - usages: [RENDER, DEPLOY]
    serviceAccount: deployer@my-project-id.iam.gserviceaccount.com

The Skaffold file points at a Cloud Run service manifest, with a dev and prod profile that apply patches such as a different minScale:

# skaffold.yaml
apiVersion: skaffold/v4beta13
kind: Config
metadata:
  name: catalog-api
manifests:
  rawYaml:
    - run.yaml
deploy:
  cloudrun: {}
profiles:
  - name: dev
    patches:
      - op: add
        path: /manifests/rawYaml/-
        value: run-dev-overrides.yaml
  - name: prod
    patches:
      - op: add
        path: /manifests/rawYaml/-
        value: run-prod-overrides.yaml

The run.yaml is a Knative-style Cloud Run service whose image field is replaced at render time with the image passed to the release. Register everything, create a release and drive it forward:

gcloud deploy apply --file=clouddeploy.yaml --region="$REGION"

gcloud deploy releases create "rel-$(date +%Y%m%d-%H%M)" \
  --delivery-pipeline=bookshelf-pipeline --region="$REGION" \
  --images="catalog-api=$REGION-docker.pkg.dev/$PROJECT_ID/bookshelf/catalog-api@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

gcloud deploy rollouts list --delivery-pipeline=bookshelf-pipeline \
  --release=REL_NAME --region="$REGION"

gcloud deploy releases promote --release=REL_NAME \
  --delivery-pipeline=bookshelf-pipeline --region="$REGION"

gcloud deploy rollouts approve ROLLOUT_NAME \
  --delivery-pipeline=bookshelf-pipeline --release=REL_NAME --region="$REGION"

Replace REL_NAME and ROLLOUT_NAME with the names printed by the previous commands. Passing the digest to --images makes the release reproducible: the same bytes are deployed to every target.

To invoke Cloud Deploy from Cloud Build, add a final step that runs gcloud deploy releases create with the freshly pushed image, so Cloud Build owns build and test and Cloud Deploy owns promotion. For GKE, the target is gke: cluster: projects/my-project-id/locations/us-central1/clusters/bookshelf-gke and the Skaffold manifests are the Deployment and Service YAML, or a Helm chart or Kustomize overlay, as in Deploying Microservices on GKE.

GitHub Actions and GitLab CI with Workload Identity Federation

Older recipes for deploying from GitHub Actions or GitLab CI create a service-account key, paste its JSON into a CI secret, and activate it in the job. That approach is outdated and usually fails now: organizations created since 2024-05-03 enable the key-creation constraint by default, and a leaked key is valid until someone notices. The current approach is Workload Identity Federation (WIF): the CI platform issues a short-lived OIDC token for each job, Google Cloud’s Security Token Service exchanges it for a federated token, and the job impersonates a service account. No secret is stored in the CI system.

The pool, provider and roles/iam.workloadIdentityUser binding for GitHub are created in Identity and Access. What matters for the pipeline is the attribute condition: always restrict the provider to your repository (and ideally branch or environment) so that other repositories on the same platform cannot impersonate your service account.

Deploying Bookshelf to Cloud Run from GitHub Actions

The service account deployer needs roles/run.developer, roles/artifactregistry.writer on the bookshelf repository and roles/iam.serviceAccountUser on the runtime identity of catalog-api.

name: deploy-catalog-api
on:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write
env:
  REGION: us-central1
  IMAGE: us-central1-docker.pkg.dev/my-project-id/bookshelf/catalog-api
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - id: auth
        uses: google-github-actions/auth@v3
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github/providers/bookshelf-repo
          service_account: deployer@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v3
      - run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
      - run: |
          docker build -t "$IMAGE:${{ github.sha }}" .
          docker push "$IMAGE:${{ github.sha }}"
      - uses: google-github-actions/deploy-cloudrun@v3
        with:
          service: catalog-api
          region: ${{ env.REGION }}
          image: ${{ env.IMAGE }}:${{ github.sha }}

The workflow needs id-token: write or the token cannot be requested. Pin actions to a full commit SHA in regulated repositories, and use a GitHub environment with required reviewers on the production job so that approvals are enforced by GitHub as well as by the IAM condition.

Deploying to GKE from GitHub Actions

For GKE, fetch cluster credentials with the same federated identity (the service account also needs roles/container.developer on the cluster), then apply manifests or run Helm:

      - id: auth
        uses: google-github-actions/auth@v3
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github/providers/bookshelf-repo
          service_account: deployer@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/get-gke-credentials@v3
        with:
          cluster_name: bookshelf-gke
          location: us-central1
      - run: |
          kubectl set image deployment/catalog-api \
            catalog-api="$IMAGE:${{ github.sha }}"
          kubectl rollout status deployment/catalog-api --timeout=180s

A private cluster endpoint is not reachable from GitHub-hosted runners; use a self-hosted runner in the VPC, the connect gateway, or let Cloud Deploy perform the rollout and have the workflow only create the release.

GitLab CI

GitLab issues the OIDC token as an id_tokens variable. Create a second pool and provider for GitLab (issuer https://gitlab.com, or your self-managed URL) and restrict it to the project path:

gcloud iam workload-identity-pools create gitlab --location=global \
  --display-name="GitLab CI"

gcloud iam workload-identity-pools providers create-oidc bookshelf-gitlab \
  --location=global --workload-identity-pool=gitlab \
  --issuer-uri="https://gitlab.com" \
  --allowed-audiences="https://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/gitlab/providers/bookshelf-gitlab" \
  --attribute-mapping="google.subject=assertion.sub,attribute.project_path=assertion.project_path" \
  --attribute-condition="assertion.project_path == 'my-group/bookshelf'"

gcloud iam service-accounts add-iam-policy-binding \
  "deployer@$PROJECT_ID.iam.gserviceaccount.com" \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/gitlab/attribute.project_path/my-group/bookshelf"

Then the job writes the token to a file, builds a credential configuration, and the gcloud CLI uses it:

deploy:
  image: gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/gitlab/providers/bookshelf-gitlab
  script:
    - echo "${GITLAB_OIDC_TOKEN}" > .ci_job_jwt_file
    - gcloud iam workload-identity-pools create-cred-config
        projects/<project-number>/locations/global/workloadIdentityPools/gitlab/providers/bookshelf-gitlab
        --service-account=deployer@my-project-id.iam.gserviceaccount.com
        --output-file=.gcp_temp_cred.json
        --credential-source-file=.ci_job_jwt_file
    - gcloud auth login --cred-file=.gcp_temp_cred.json
    - gcloud run deploy catalog-api --region=us-central1
        --image=us-central1-docker.pkg.dev/my-project-id/bookshelf/catalog-api:$CI_COMMIT_SHORT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

The credential file contains no secret, only the instructions for exchanging the token file, but it should still not be committed. Older GitLab guides use the CI_JOB_JWT_V2 variable, which GitLab has removed in favour of id_tokens.

Local development: Functions Framework and emulators

Cloud Run functions (formerly Cloud Functions) are written against the open-source Functions Framework, which also runs locally, so the same code serves requests on a laptop and in production. For Java, add the framework API and run the function with the Maven plug-in:

<dependency>
  <groupId>com.google.cloud.functions</groupId>
  <artifactId>functions-framework-api</artifactId>
  <version>1.1.0</version>
  <scope>provided</scope>
</dependency>
<plugin>
  <groupId>com.google.cloud.functions</groupId>
  <artifactId>function-maven-plugin</artifactId>
  <version>0.11.0</version>
  <configuration>
    <functionTarget>com.example.bookshelf.HelloFunction</functionTarget>
  </configuration>
</plugin>

Derived from Functions Framework.

package com.example.bookshelf;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;

public class HelloFunction implements HttpFunction {
    @Override
    public void service(HttpRequest request, HttpResponse response) throws Exception {
        response.getWriter().write("catalog ok");
    }
}

Derived from Write HTTP functions.

Start it with mvn function:run and call http://localhost:8080. The versions above are the ones in the official sample at the time of writing; use the latest from Maven Central. Event-driven functions (Pub/Sub, Cloud Storage, Eventarc) are covered in Cloud Run Functions and Events.

Local emulators let tests run without a network connection, a project or a bill. The gcloud CLI ships emulators for Pub/Sub, Firestore, Bigtable, Datastore and Spanner; Cloud Storage has no official one, and the community fake-gcs-server fills the gap:

gcloud components install beta pubsub-emulator cloud-firestore-emulator

gcloud beta emulators pubsub start --project=my-project-id --host-port=localhost:8085
# in another terminal
$(gcloud beta emulators pubsub env-init)

gcloud emulators firestore start --host-port=localhost:8086

docker run --rm -p 4443:4443 fsouza/fake-gcs-server -scheme http

The client libraries detect the emulators through environment variables: PUBSUB_EMULATOR_HOST and FIRESTORE_EMULATOR_HOST make the Java clients talk to localhost with no credentials, and the Cloud Storage client accepts a custom endpoint through StorageOptions.newBuilder().setHost("http://localhost:4443"). The Spanner, Bigtable and Datastore emulators follow the same pattern with SPANNER_EMULATOR_HOST, BIGTABLE_EMULATOR_HOST and DATASTORE_EMULATOR_HOST. Emulators do not enforce IAM, quotas or exact latency, so keep a few integration tests against real services. For container-based test dependencies see Testcontainers Fundamentals.

Software supply-chain security

Three services turn a pipeline into an auditable chain from source to running container.

Artifact Analysis

Scans images pushed to Artifact Registry for known vulnerabilities and stores the results and metadata as occurrences. Automatic scanning is switched on by enabling the Container Scanning API (containerscanning.googleapis.com), as shown in Container Registry and Builds.

SLSA provenance from Cloud Build

Cloud Build can generate signed provenance for images it builds, describing the source, builder and build steps. It is requested per build, and the provenance is stored in Artifact Analysis next to the image. It targets the SLSA build track at level 3 for container images built by Cloud Build.

Binary Authorization

A deploy-time policy that admits only images that have required attestations. When the Binary Authorization API is enabled, an attestor named built-by-cloud-build is created and Cloud Build attests images it built, so a policy can demand "built by our pipeline" without any key management. It is enforced on Cloud Run, GKE and GKE Enterprise.

In the Google Cloud console

  1. Open Security > Binary Authorization (enable the API if prompted).

  2. Choose Policy > Edit policy, set the default rule to Require attestations and add the attestor built-by-cloud-build.

  3. Set the enforcement mode to Block and audit log, and save. Start with Dry run if existing workloads would be rejected.

  4. On a Cloud Run service enable Binary Authorization under security settings so that deployments are checked.

With the gcloud CLI

cat > policy.yaml <<EOF
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
  requireAttestationsBy:
    - projects/$PROJECT_ID/attestors/built-by-cloud-build
globalPolicyEvaluationMode: ENABLE
name: projects/$PROJECT_ID/policy
EOF

gcloud container binauthz policy import policy.yaml

gcloud run deploy catalog-api --region="$REGION" \
  --image="$REGION-docker.pkg.dev/$PROJECT_ID/bookshelf/catalog-api:1.0.0" \
  --binary-authorization=default

Provenance is requested with options.requestedVerifyOption: VERIFIED in cloudbuild.yaml; with it set, a build publishes provenance and attestations only when the build is verifiably clean. Pair these controls with the cleanup and scanning settings in Container Registry and Builds and with image hygiene from Image Best Practices and Supply-Chain Security.

Infrastructure as code in the pipeline

Pipelines should create the environment as well as deploy to it. Terraform with the hashicorp/google provider (v8) is the common choice; Infrastructure Manager runs Terraform as a managed service inside Google Cloud, and Deployment Manager is retired (support ended 2026-04-01 and shutdown is scheduled for 2027-06-30). The setup, remote state in Cloud Storage, a Bookshelf baseline module and the migration from Deployment Manager are in Infrastructure as Code.

A typical split keeps infrastructure and application delivery in separate pipelines: a Terraform pipeline runs terraform plan on pull requests and apply on merge, authenticating through WIF as described above, with a service account that holds broad infrastructure roles; the application pipeline holds only the narrow deployer roles shown on this page. The plan output is posted to the pull request for review.

Source repositories

Cloud Source Repositories, Google’s original hosted Git service, has been closed to new customers since 2024-06-17. Existing customers can keep using it, but Google recommends moving, and older tutorials that begin with gcloud source repos create or mirror a repository into it are outdated. Where to host code now:

GitHub, GitLab or Bitbucket

Connect them to Cloud Build through a 2nd-generation repository connection; the 1st-generation GitHub App integration is legacy. Connections and triggers are in Container Registry and Builds.

Developer Connect

A Google Cloud service that manages connections to external source providers (GitHub, GitLab and Bitbucket) and links them to build, deploy and Gemini features, so that tools in Google Cloud can read the repository without personal tokens.

Secure Source Manager

A managed, single-tenant Git service in Google Cloud with pull requests, issues and built-in integrations, for organizations that need source hosting inside their own security boundary. It is billed per instance while it exists, so create one only if you need it.

To migrate an existing Cloud Source Repositories repository, list it, clone it, create the destination repository on the new host and push every ref:

gcloud source repos list
gcloud source repos clone bookshelf --project="$PROJECT_ID"
cd bookshelf
git remote add github git@github.com:my-org/bookshelf.git
git push github --all
git push github --tags

Git fundamentals and GitHub workflows are in Git & GitHub.

Pricing model, limits and launch stages

Pricing

Cloud Code is free; Gemini Code Assist has a free tier for individuals and paid per-user editions. Cloud Workstations bills the cluster management fee per hour, the VM compute while a workstation runs (including idle pool VMs), and the persistent disks. Cloud Build bills per build minute by machine type with a free monthly allowance for the default machine. Cloud Deploy bills per active delivery pipeline per month, with a free allowance; rendering and deployment run in Cloud Build and are billed there. Binary Authorization and Artifact Analysis scanning are billed per policy evaluation or scanned image. Check the dated pricing pages linked below, because rates change.

Cost drivers and controls

Workstations left running (set short idle and running timeouts), workstation clusters that nobody uses, long or uncached builds, large pools of ready VMs, and scanning of images that are never deployed.

Limits

Cloud Build and Cloud Deploy have per-region quotas for concurrent builds, pipelines, targets and releases; GitHub Actions and GitLab runners have their own limits. See the quota pages in the references.

Launch stages and dated changes

Cloud Source Repositories closed to new customers on 2024-06-17. Container Registry shut down on 2025-03-18. Deployment Manager support ends 2026-04-01. Cloud Build 1st-generation repository integrations are legacy. Gemini Cloud Assist and some Gemini Code Assist agent features have been in Preview; check the release notes for the status of any feature you depend on.

Troubleshooting

Unable to acquire impersonated credentials or PERMISSION_DENIED from google-github-actions/auth

The workflow lacks id-token: write, the provider’s attribute condition does not match the repository or branch, or the service account is missing roles/iam.workloadIdentityUser for that principal. Compare the token claims with the condition.

The caller does not have permission on iam.serviceAccounts.actAs

The deployer needs roles/iam.serviceAccountUser on the identity the Cloud Run service or Cloud Deploy target runs as.

Cloud Deploy release fails at render

The Skaffold configuration or profile names do not match the pipeline stage, or the execution service account cannot read the Skaffold source bucket. Read the render log in the release page.

Rollout waits forever

The target has requireApproval: true. Approve it with gcloud deploy rollouts approve.

Binary Authorization denies a deployment

The image has no attestation from the required attestor, usually because it was not built by Cloud Build or was pushed by another tool. The audit log entry lists the missing attestor.

Workstation will not start

The cluster subnet lacks Private Google Access or required firewall rules, or the machine type is out of stock in the zone. Check the workstation’s logs and the cluster’s network settings.

gcloud source repos create fails with an access error

The project is not an existing Cloud Source Repositories customer; the service is closed to new customers.

Clean up

Delete in dependency order. A throwaway project can be deleted instead, which removes everything after a 30-day recovery window.

gcloud workstations delete bookshelf-dev --cluster=bookshelf-ws --config=bookshelf-config --region="$REGION"
gcloud workstations configs delete bookshelf-config --cluster=bookshelf-ws --region="$REGION"
gcloud workstations clusters delete bookshelf-ws --region="$REGION"

gcloud deploy delete --file=clouddeploy.yaml --region="$REGION" --force

gcloud iam workload-identity-pools providers delete bookshelf-gitlab \
  --location=global --workload-identity-pool=gitlab
gcloud iam workload-identity-pools delete gitlab --location=global

gcloud run services delete catalog-api --region="$REGION"

Derived from Delete a workstation and gcloud deploy delete. Workload Identity pools are soft deleted and can be restored for 30 days; their names cannot be reused until they are purged. Also reset the Binary Authorization policy to allow all (gcloud container binauthz policy import with the default policy) if it was changed, remove the GitHub environment and repository variables you created, and revoke the Cloud Build GitHub App installation in GitHub. See Shutting down projects.