This article provides step by step guide for setting up a complete CI/CD pipeline to deploy a Express API to Google Cloud Run using GitHub Actions.

What We'll do?

  • Builds a Docker container from your Express.js application
  • Pushes the container to Google Artifact Registry
  • Deploys the container to Google Cloud Run

All of this happens automatically whenever you push to your main branch.

Requirement before you start

  • A Google Cloud Platform account
  • A GitHub account
  • Basic familiarity with Node.js and Express
  • Git installed on your local machine

Step 1: Create a Simple Express Application

Let's start by creating a basic Express API. Create a new directory for your project and initialize it:

mkdir cloud-run-api
cd cloud-run-api
npm init -y
npm install express body-parser cors dotenv

Now, create an index.js file with the following content:

const express = require("express");
const bodyParser = require("body-parser");
require("dotenv").config();
const app = express();
const cors = require("cors");

app.use(cors());
app.use(express.json());
app.use(bodyParser.json());

app.get("/", (req, res) => {
    res.status(200).send("OK");
});

// Health check route sometime required by Cloud Run
app.get("/health", (req, res) => {
   res.status(200).send("Healthy");
});

app.get("/greeting", (req, res) => {
   res.status(200).send({
        message: "Hello, World!"
   });
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => console.log(`Server started at port: ${port}`));

Important: Note the /health endpoint is sometime required by Cloud Run to properly verify your application is running.
if you miss to add, you may get following error

(gcloud.run.deploy) Revision 'github-cloud-run-service-00003-lbd' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable within the allocated timeout. This can happen when the container port is misconfigured or if the timeout is too short

Update your package.json to include a start script:

{
  "scripts": {
    "start": "node index.js"
  }
}

Step 2: Containerize Your Application with Docker

Create a Dockerfile in your project root:

FROM node:22

# Create app directory
WORKDIR /app

# Install app dependencies
COPY package*.json ./
RUN npm install

# Bundle app source
COPY . .

# Ensure the PORT environment variable is properly used
EXPOSE 8080

# Start the server
CMD ["npm", "start"]

This Dockerfile:

  • Uses the official Node.js image
  • Installs dependencies before copying the application code (leveraging Docker layer caching)
  • Exposes port 8080 (though Cloud Run injects the actual port via environment variable)
  • Uses npm start to launch your application

Step 3: Enable Required Google Cloud APIs

Before continuing, you need to enable several Google Cloud APIs:

  1. Go to the Google Cloud Console
  2. Select or create a project
  3. Navigate to "APIs & Services > Library"
  4. Search for and enable:
    • Artifact Registry API
    • Cloud Run API
    • Cloud Build API
    • iAm API

Step 4: Create a Service Account for with necessary permissions for deployment

You'll need a service account :

  1. Go to IAM & Admin > Service Accounts in Google Cloud Console
  2. Click Create Service Account
  3. Enter a name and description for your service account
  4. Click "Create and Continue"
  5. Add the following roles:
    • Artifact Registry Admin: For managing container images
    • Cloud Run Admin: For deploying and managing Cloud Run services
    • Service Account User: For acting as the service account
  6. Click "Done"
  7. Once created, select the service account and go to the Keys tab
  8. Click Add Key > Create new key
  9. Choose JSON and click Create

A JSON key file will be downloaded to your computer. Keep this file secure!

Step 5: Add GitHub Repository Secrets

Now, let's add the necessary secrets to your GitHub repository:

  1. Open your GitHub repository in a browser
  2. Go to Settings > Secrets and variables > Actions
  3. Add the following repository secrets:
Secret Name Value
GCP_DEPLOY_SA_KEY The entire content of the JSON key file downloaded earlier
GCP_PROJECT_ID Your Google Cloud Project ID
GCP_PROJECT_SA_NAME Email address of your service account
REPOSITORY_NAME Name of your Artifact Registry repository (created in next step)
GCP_LOCATION Your preferred Google Cloud region (e.g., us-central1)

Note: After adding these secrets, delete the JSON key file from your local machine.

Step 6: Create an Artifact Registry Repository to store Docker images

  1. Visit the Artifact Registry in Google Cloud Console
  2. Click Create Repository
  3. Enter a name (this will be your REPOSITORY_NAME secret)
  4. Choose a region (this should match your GCP_LOCATION secret)
  5. Select Docker as the format
  6. Leave other fields as default
  7. Click Create

Step 7: Set Up the GitHub Actions Workflow

Create a .github/workflows directory in your project and add a deploy.yaml file:

name: Build and Deploy to Cloud Run

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Authenticate with Google Cloud
      uses: google-github-actions/auth@v1
      with:
        credentials_json: ${{ secrets.GCP_DEPLOY_SA_KEY }}

    - name: Set up Google Cloud SDK
      uses: google-github-actions/setup-gcloud@v1
      with:
        project_id: ${{ secrets.GCP_PROJECT_ID }}

    - name: Configure Docker to use gcloud as a credential helper
      run: |
        gcloud auth configure-docker ${{ secrets.GCP_LOCATION }}-docker.pkg.dev

    - name: Build and Push Docker image
      run: |
        IMAGE_URI="${{ secrets.GCP_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.REPOSITORY_NAME }}/my-github-action:latest"
        echo "Building Docker image: $IMAGE_URI"
        docker build -t "$IMAGE_URI" .
        docker push "$IMAGE_URI"

    - name: Deploy to Cloud Run
      run: |
        gcloud run deploy github-cloud-run-service \
          --image "${{ secrets.GCP_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.REPOSITORY_NAME }}/my-github-action:latest" \
          --platform managed \
          --region ${{ secrets.GCP_LOCATION }} \
          --port 8080 \
          --project ${{ secrets.GCP_PROJECT_ID }} \
          --service-account ${{ secrets.GCP_PROJECT_SA_NAME }} \
          --allow-unauthenticated

Note the --allow-unauthenticated flag, which makes your service publicly accessible.

Step 8: Commit and Push Your Code

Initialize a Git repository, commit your changes, and push to GitHub:

git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/your-username/your-repo-name.git
git push -u origin main

GitHub action will automatically deploy you API to cloud run

Testing Our API using the Cloud Run URL

  1. Go to the Cloud Run section in Google Cloud Console
  2. Click on your service name (github-cloud-run-service)
  3. At the top of the overview page, you'll find the URL to your deployed service (usually something like https://github-cloud-run-service-abc123-uc.a.run.app)
  4. Test API end point directly in any browser (Accessible because we add --allow-unauthenticated flag in Yaml file)