After revisiting the basics of cloud infrastructure in my previous post, I figured it's a good time to talk about something equally essential: CI/CD.
In the past few years working with Google Cloud, I've had to set up multiple CI/CD pipelines—some simple, some complex. I’ve found that using Cloud Build makes it straightforward to automate deployments and integrate well with other GCP services.
In this post, I’ll walk through how to build a CI/CD pipeline using Cloud Build, and how it fits into a modern DevOps workflow on GCP.
What is Cloud Build?
Cloud Build is a serverless CI/CD platform from Google Cloud that lets you build, test, and deploy your code directly from repositories like GitHub or Cloud Source Repositories. It supports custom build steps using Docker images and integrates smoothly with other GCP services like Cloud Run, GKE, and Artifact Registry.
Setting Up Service Account and Required Roles for Cloud Build Deployment
Before using Cloud Build, you need to create a dedicated service account for it, which can be either the default service account or a custom one. Then, assign the necessary roles based on your build requirements.
In this post, since I will deploy the application to App Engine — a fully managed platform by Google Cloud that automatically handles infrastructure, scaling, and load balancing for web applications — I assign the following roles to the service account:
- App Engine Deployer for deploying the app
- Storage Admin because the staging process involves Cloud Storage, and
- Logs Writer to allow logging build activities to Cloud Logging.
After creating the service account, the next step is to set up a connection to our code repository. In this example, I’m connecting to GitLab, so I create a host connection to my GitLab repository.
Once connected, you will see something like the image above, where the connection name and the linked repository appear in Cloud Build.
Creating a CI/CD Trigger in Cloud Build
To create a CI/CD trigger, go to the Cloud Build page and select the Triggers menu, then click "+ Create Trigger".
On this page, you can specify the event that will invoke the build trigger. These events can be repository-related, such as a push to a branch, a new tag push, or a pull request. Alternatively, the trigger can respond to non-repository events like manual invocation, Pub/Sub messages, or webhook events.
Next, select the repository to connect. Then, choose the build configuration method—whether based on a Cloud Build config file, a Dockerfile, or Buildpacks. In this example, I will use a Cloud Build configuration file (cloudbuild.yaml).
As an additional note, you can also add environment variables to the trigger using the Substitution variables option. However, the variable names must include an underscore and follow the format _VARIABLE; using names without the underscore (like VARIABLE) is not allowed.
Finally, select the service account that you created earlier to run the build.
Cloud Build Configuration for Deployment
The build configuration file (cloudbuild.yaml) is stored in the code repository alongside the application source code. This allows Cloud Build to automatically detect and use the configuration whenever a build is triggered.
Since in this example I am using App Engine to deploy a Node.js application, besides the Cloud Build configuration, there is also an app.yaml file.
The app.yaml file is the configuration file for App Engine that defines deployment settings such as the runtime environment, instance class, service name, and URL handlers.
Here is an example of the app.yaml content:
env: standard # Specifies the App Engine environment (standard or flexible)
runtime: nodejs20 # Defines the runtime environment (Node.js version 20)
instance_class: F2 # Specifies the instance class which controls CPU and memory resources
service: testing # Names the service/module for this deployment
handlers: # Defines how URL paths are handled
- url: /.* # Matches all URL paths
secure: always # Enforces HTTPS for all requests
script: auto # Automatically selects the script to run (default behavior for Node.js)
Below is an example of a Cloud Build configuration (cloudbuild.yaml) that runs several build steps before deploying the app to App Engine:
steps:
- name: 'node'
entrypoint: 'yarn'
args: ['cache', 'clean']
- name: 'node'
entrypoint: 'yarn'
args: ['install']
- name: 'node'
entrypoint: 'yarn'
args: ['build']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: 'bash'
args: ['-c', 'gcloud config set app/cloud_build_timeout 1600 && gcloud app deploy']
timeout: '1600s'
options:
logging: CLOUD_LOGGING_ONLY
Explanation of each step:
- yarn cache clean clears the local yarn cache to ensure a fresh install of dependencies.
- Install Dependencies: yarn install installs the project dependencies as defined in package.json.
- yarn build runs the build script, typically compiling or bundling the app for production.
- Uses the Google Cloud SDK container to run a bash command that sets the build timeout and deploys the app with gcloud app deploy.
- The timeout: '1600s' defines the maximum time the build can run before being cancelled (in this case, about 26 minutes).
- The options.logging: CLOUD_LOGGING_ONLY setting ensures that build logs are sent only to Cloud Logging.
Monitoring Build Progress in Cloud Build
You can monitor the progress and status of your builds in the Build History page of Cloud Build. This page provides detailed logs for each build step, allowing you to troubleshoot issues or confirm successful deployments. Each entry shows timestamps, build results, and any error messages if the build fails.
With this setup, you now have a complete CI/CD pipeline that automates building and deploying your Node.js application to App Engine. By integrating Cloud Build with your repository and configuring triggers, deployment becomes consistent, repeatable, and easier to manage. In future posts, I plan to explore more advanced topics such as environment variable management, artifact storage, and secret handling to further optimize the pipeline.
Top comments (0)