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:
- Go to the Google Cloud Console
- Select or create a project
- Navigate to "APIs & Services > Library"
- 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 :
- Go to
IAM & Admin > Service Accounts
inGoogle Cloud Console
- Click
Create Service Account
- Enter a
name
anddescription
for your service account - Click "Create and Continue"
- 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
- Click "Done"
- Once created, select the service account and go to the
Keys
tab - Click
Add Key > Create new key
- Choose
JSON
and clickCreate
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:
- Open your GitHub repository in a browser
- Go to
Settings > Secrets and variables > Actions
- 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
- Visit the Artifact Registry in Google Cloud Console
- Click
Create Repository
- Enter a name (this will be your
REPOSITORY_NAME
secret) - Choose a
region
(this should match yourGCP_LOCATION
secret) - Select
Docker
as the format - Leave other fields as default
- 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
- Go to the
Cloud Run
section inGoogle Cloud Console
- Click on your service name (github-cloud-run-service)
- 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)
- Test API end point directly in any browser (Accessible because we add --allow-unauthenticated flag in Yaml file)