How To Build a Simple CI/CD Pipeline using Docker, Github Actions, and Google Cloud Run

D'Riski Maulana
7 min readJun 23, 2023

Introduction

I guess all of you who come to this article is already familiar with the CI/CD terminology. However, i will slightly give you an understanding of CI/CD.

In the ever-evolving world of software development, the ability to deliver high-quality applications rapidly and consistently is crucial. This is where Continuous Integration and Continuous Deployment (CI/CD) practices come into play. CI/CD empowers development teams to automate and streamline their software delivery processes, enabling them to iterate quickly, detect issues early, and deploy with confidence.

Understanding the Components

  • Github Actions, a feature of Github that provides a flexible and scalable workflow automation platform. In the CI/CD pipeline, Github Actions will be used to execute pipeline stages, including building, testing, and deploying the application.
  • Docker, a tool used for containerization, which enables us to package our application and its dependencies into a lightweight, portable unit. In the CI/CD pipeline, Docker is utilized to ensure consistent and reproducible environments across various stages. The Docker image is the one that will be deployed to Cloud Run.
  • Artifact Registry, a tool that simplifies the management and storage of software artifacts, including Docker images, Maven packages, and npm packages. In the CI/CD pipeline, Artifact Registry is utilized as a repository to store the build image results for every image revision.
  • Google Cloud Run, a fully managed serverless platform that enables us to run containers with automatic scaling. In the CI/CD pipeline, we will leverage this Google Cloud service to deploy our application.

Setting Up the Development Environment

  1. NodeJS Application

Use your own application, or if you don’t have one you can clone this github repository to follow this tutorial driskimaulana/plain-expressjs

2. Set-up the Google Cloud Project

Open the Google Cloud Console and access the Cloud Shell located in the top corner of the page.

a. Create a Google Cloud Project

gcloud projects create ${YOUR_PROJECT_NAME}
gcloud config set project ${YOUR_PROJECT_NAME}

b. Enable the required service (Cloud Run and Artifact Registry)

gcloud services enable run.googleapis.com
gcloud services enable artifactregistry.googleapis.com

c. Create services account with the required roles: (Artifact Registry Admin, Cloud Run Admin, Service Account User, and Storage Admin)

Create Service accounts:

gcloud iam service-accounts create ci-cd-github-actions --display-name "CICD Github Actions"

Add roles to service account:

gcloud projects add-iam-policy-binding ${PROJECT_NAME} --member=serviceAccount:ci-cd-github-actions@${PROJECT_NAME).iam.gserviceaccount.com --role=roles/run.admin
gcloud projects add-iam-policy-binding ${PROJECT_NAME} --member=serviceAccount:ci-cd-github-actions@${PROJECT_NAME).iam.gserviceaccount.com --role=roles/storage.admin
gcloud projects add-iam-policy-binding ${PROJECT_NAME} --member=serviceAccount:ci-cd-github-actions@${PROJECT_NAME).iam.gserviceaccount.com --role=roles/iam.serviceAccountUser
gcloud projects add-iam-policy-binding ${PROJECT_NAME} --member=serviceAccount:ci-cd-github-actions@${PROJECT_NAME).iam.gserviceaccount.com --role=roles/artifactregistry.admin

d. Generate a JSON file for the service account. This key will be used to authenticate GitHub workflows with Google Cloud.

gcloud iam service-accounts keys create ./key.json --iam-account=ci-cd-github-actions@${PROJECT_NAME).iam.gserviceaccount.com

3. Artifact Registry Repository

Next, we will need a repository to store the build results of our application and use it as the source for deploying to Cloud Run.

a. In the Google Cloud Console, locate the “Artifact Registry” menu in the sidebar and click on it.

b. Click “Create Repository” in the top menu.

Artifact Registry Service

c. Add the necessary details for the repository. To keep it simple, follow my configurations and adjust the name and region according to your needs.

NB: Remember to write down the repository name as it will be used later in the github secrets.

4. Set Up Github Secret Keys

In the current project repository on GitHub, navigate to “Settings > Secrets & Variables > Actions > New Repository”.

Github Actions Secrets and Variables

Add this key and value to the repository secrets by clicking the green button.

a. Project ID

Add your Google Cloud project name or ID to the GitHub environment.

Key: GCP_PROJECT_ID
Value: ${YOUR_PROJECT_NAME}

Example:
Key: GCP_PROJECT_ID
Value: ci-cd-with-actions

b. Project Region

Specify the region where you want the service to be deployed.

Key: GCP_REGION
Value: ${YOUR_PROJECT_REGION}

Example:
Key: GCP_REGION
Value: asia-southeast2

c. Service Account Keys

In the Google Cloud Shell, execute the following command to retrieve the service account key:

cat ./key.json

This command will print the service account key that we created in the previous step. Copy the output of the command and paste it into the GitHub Action Secrets and Variables.

Key: GCP_CREDENTIALS
Value: ${YOUR_GENERATED_SERVICE_ACCOUNT_KEY_JSON}

d. Repository Name

Add the repository name that we created in step three to the configuration.

Key: GCP_ARTIFACT_REPOSITORY
Value: ${YOUR_REPOSITORY_NAME}

e. Service Name

This name will become the Cloud Run service name and will be part of your service endpoint URLs. For example:

Cloud Run service name

Add the service name that you want to deploy.

Key: GCP_SERVICE_NAME
Value: ${YOUR_SERVICE_NAME}

After completing the above step, the page will have a similar appearance to this:

Github Actions secrets and variables

5. Create Dockerfile for the application

To create the Dockerfile, navigate to the project folder and create a file named “Dockerfile”. This file will contain a set of instructions for building the Docker image, including the necessary dependencies, configurations, and commands to run the application.

Dockerfile in project tree

Open the Dockerfile and add the following code:

./Dockerfile

FROM node:16-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install dependencies.
RUN npm ci --only=production

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
ENTRYPOINT [ "node", "index.js" ]

The above code is for a Node.js application. Adjust the Dockerfile code above to meet the specific requirements of your application.

Don’t forget to add a .dockerignore file to exclude certain files or folders that you don’t want to include when building the image.

.dockerignore

# Exclude locally installed dependencies
./src/node_modules/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.gitignore

Build the CI/CD Pipeline

In this section, we will build the code that can be utilized by GitHub Actions.

  1. In the project directory, create a folder to store the YML code that can be used by GitHub Actions.

./.github/workflows/google-cloud-run-docker.yml

Yml code in project tree

2. Open the ‘google-cloud-run-docker.yml’ file and copy the following code:

name: Build and Deploy to Cloud Run

on:
push:
branches: [ "master" ]

env:
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} # TODO: update Google Cloud project id
SERVICE: ${{ secrets.GCP_SERVICE_NAME }} # TODO: update Cloud Run service name
REGION: ${{ secrets.GCP_REGION }} # TODO: update Cloud Run service region
REPOSITORY: ${{ secrets.GCP_ARTIFACT_REPOSITORY }}

jobs:
deploy:
# Add 'id-token' with the intended permissions for workload identity federation
permissions:
contents: 'read'
id-token: 'write'

runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2

# NOTE: Alternative option - authentication via credentials json
- name: Google Auth
id: auth
uses: 'google-github-actions/auth@v0'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

# Authenticate Docker to Google Cloud Artifact Registry
- name: Docker Auth
id: docker-auth
run: docker login -u _json_key -p '${{ secrets.GCP_CREDENTIALS }}' https://${{ env.REGION }}-docker.pkg.dev

- name: Build and Push Container
run: |-
docker build -t "${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" ./
docker push "${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}"

# END - Docker auth and build

- name: Deploy to Cloud Run
id: deploy
run: gcloud run deploy ${{ env.SERVICE }} --image ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }} --platform managed --region ${{ env.REGION }} --allow-unauthenticated --max-instances=5

# If required, use the Cloud Run url output in later steps
- name: Show Output
run: echo ${{ steps.deploy.outputs.url }}

3. After that, commit and push to the GitHub master branch repository, and then check the Actions tab on the GitHub repository page.

Github Actions page after push

Click on the latest workflow run to view the details of the latest commit job.

Detail of the Github Actions jobs

4. To obtain the deployment URLs, navigate to the Google Cloud Console, locate the Cloud Run services in the sidebar, and click on the service name.

Cloud Run Details

Here, you can find the deployment URLs next to the Cloud Run service name.

Deployed Service

Now, whenever a new commit is pushed to the master branch, it will be automatically deployed, ensuring that the service is always up to date with the latest changes. Congratulations 👏👏

Conclusion

By following this guide, you’ve learned how to build a CI/CD pipeline using Docker, GitHub Actions, and Google Cloud Run. This pipeline enables you to automate the build, test, and deployment processes, ensuring efficient and reliable software delivery. Leveraging these technologies, you can significantly streamline your development workflow, maintain consistent environments, and achieve faster feedback loops, ultimately improving the overall software quality and deployment efficiency. Start integrating CI/CD pipelines into your projects today and experience the benefits of automated software delivery.

Remember, building a CI/CD pipeline is an iterative process. Continuously improve and adapt it to suit your project’s specific requirements and scale as your development needs evolve. Happy coding and deploying! 👋👋

--

--

D'Riski Maulana

👨‍💻 Software Engineer passionate about crafting elegant code. Exploring the tech landscape one challenge at a time. 🌐✨