We have built our Cloud Native - Continuous Integration / Continuous Deployment pipeline with GCP to build, test and deploy an application (Next.js) with interim status notifications.

Docker has been used extensively in this pipeline, from building the application, testing the application through webdriverIO docker service in Selenium Hub - chrome/firefox setup and then persist application image in the repository and finally deploy the image.

At this juncture, we would also like to highlight the challenges that we have resolved while setting up this pipeline, While the first was more related to Ui tests as a git submodule to Next.js application repository (please have a look into our blog here) and next one was related to passing multiple NEXT_PUBLIC env variables with docker at runtime (have a look into our past blog here for more detail).

Pipeline Overview

Let’s get into our Cloud Native CI/CD pipeline as shown below with multiple steps. Each step gets executed with a cloud component. Within Cloud Build, we have Cloud Builder to execute that step in the build pipeline. Cloud Builders are container images used to execute the step and have added a list of cloud builders used in this pipeline at last in the Appendix section. All the steps are defined in the cloudbuild.yml file and the pipeline gets started with the help of Cloud Build Trigger. With this pipeline, build gets triggered with a push to main branch.


Pipeline Steps

Build process gets started by pulling the code with submodules from cloud source repositories using cloud builder gcr.io/cloud-builders/git and then pipeline gets to the next step, to build application images with docker. For this, we have a docker-compose.yml file, with cloud builder docker/compose:1.19.0 executing this step, to bring up services like application (Next.js app), selenium-hub, chrome/firefox and have them running on their defined ports and within the cloudbuild network. This cloudbuild network allows them to communicate with each other like, Ui tests will use application service to test the image.

With multiple NEXT_PUBLIC env variables, application service will take time to load them. So, we have added a healthcheck to the application service that ensures the application is running. Also, If docker takes more time to have the service up or has some issues, passing timeout value (seconds) will stop the pipeline.

Moving on to the next step - Test Execution, webdriverIO tests get executed by installing the test dependencies from package.json and start running the test on host with cloud builder node:18. At the end of test execution, with hook class, we are writing the test Exit Code (0 Pass / 1 Fail) to a file and sharing the test status via Google Chat webhook. Here, tests might fail. So, setting allow_failure: true to proceed with the next pipeline step. After test execution gets completed, we are pushing the test results to cloud bucket with cloud builder gcr.io/cloud-builders/gsutill. With this, our Continuous Integration pipeline steps gets completed and let’s get into Continuous Deployment pipeline steps.

Before pushing the image to Container Registry, we have Check Gate to verify the test results and in check gate, we are reading the test exit code file using cloud builder ubuntu. For test failures with exit code 1 - Cloud Build pipeline gets stopped ! With exit code 0 (all tests passed), pipeline gets to the next step - Push Image. Using cloud builder gcr.io/cloud-builders/docker we are tagging the image to latest and pushing it to the Container Registry. Then, the image with latest tag from Container Registry is deployed to Cloud Run with cloud builder gcr.io/google.com/cloudsdktool/cloud-sdk and the application is available to public users.

Between the deployment pipeline steps, we are using cloud builder curlimages/curl to share pipeline step notifications via GoogleChat webhook. Also, we have a step to clear last run test results using cloud builder gcr.io/cloud-builders/gsutill and it gets executed first before pulling the code. Sometimes the last build might have failed in between and no test results to clear. So, we are setting allow_failure: true to proceed with the next pipeline step.