DEV Community

Rashika Karki
Rashika Karki

Posted on

Deploy to Google Cloud Run in Minutes

Ever wanted to deploy your app to the cloud but got lost in a maze of documentation? Let's fix that! Today, we're deploying a simple server to Google Cloud Run with GitHub Actions automation. By the end of this guide, every push to your main branch will automatically deploy your app. Magic? Nope, just good DevOps!

Psst! This guide was created for the Cloud Run Hackathon. If you're participating, this tutorial will help you get started with the basics. Cloud Run supports services, jobs, and worker pools - perfect for building AI-powered apps with Gemini, handling background tasks, or processing streams from Pub/Sub.

What We're Building

A simple Node.js Express server that says "Hello from Cloud Run!" - perfect for testing deployments. Once you've got this working, you can swap it out for your own app.

Step 1: Your Super Simple Server

Create a file called server.js:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 8080;

app.get('/', (req, res) => {
  res.json({ 
    message: 'Hello from Cloud Run!', 
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV || 'development'
  });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

And your package.json:

{
  "name": "cloud-run-demo",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Just 20 lines of actual server code. Simple, clean, deployable.

Step 2: Create Your Google Cloud Project

Head over to the Google Cloud Console and let's get cooking:

  1. Create a new project
    • Click on the project dropdown at the top

Project dropdown

  • Click "New Project"

New project button

  • Give it a memorable name (like my-awesome-app)
  • Save the Project ID - you'll need this later!

Step 3: Set Up Cloud Run

  1. Search for "Cloud Run" in the search bar and select the product

Cloud Run search

  1. Click "Deploy Container"

Deploy container button

  1. Fill in the basics:

Cloud Run configuration

  • Service name: hello-server (or whatever makes you happy)
  • Region: europe-west1 (or choose your nearest region)
  • Authentication: Allow public access (for our demo)
  • Container image URL: Leave empty for now (we'll add it via GitHub Actions)

Don't worry if you see an error about the missing container - that's expected! We'll fix it when we deploy.

Save these values:

  • Service name (e.g., hello-server)
  • Region (e.g., europe-west1)

Step 4: Set Up Artifact Registry

This is where Docker will store your container images.

  1. Search for "Artifact Registry" and select the product

Artifact Registry search

  1. Click "Create Repository"

Create repository button

  1. Configure:
    • Format: Docker
    • Name: my-app-repo (keep it simple!)
    • Region: Same as your Cloud Run region
    • Mode: Standard

Artifact Registry config

  1. Click Create

Save the repository name for later!

Step 5: Create a Service Account

This gives GitHub permission to deploy on your behalf.

  1. Search for "Credentials" and go to the product page

Credentials search

  1. Click "Create Service Account"

Create service account

  1. Name it: github-deployer

  2. Add these permissions:
    When creating your service account (github-deployer), you’ll be asked to assign roles.
    These roles define what your GitHub Actions workflow is allowed to do inside your Google Cloud project.

Role What it Does Why It’s Needed
Artifact Registry Reader Reads Docker images from the registry So Cloud Run can fetch the image
Artifact Registry Writer Pushes new images to the registry So GitHub Actions can upload builds
Cloud Run Admin Creates and updates Cloud Run services To deploy your app
Service Account User Acts as the runtime service account To let Cloud Run run your container

Service account permissions

  1. Click on your new service account → "Keys" tab

Keys tab

  1. "Add Key" → "Create new key" → JSON

Create JSON key

  1. Download the JSON file (keep it safe!)

Download key

Step 6: Configure GitHub Secrets

In your GitHub repository:

  1. Go to Settings → Secrets and variables → Actions

GitHub secrets navigation

  1. Click "New repository secret"

New secret button

Secret form

  1. Add these five secrets:
Secret Name Value
GCP_SA_KEY Paste the entire JSON file content
PROJECT_ID Your Google Cloud project ID
PROJECT_REGION Your region (e.g., europe-west1)
ARTIFACT_REGISTRY_REPO Your Artifact Registry name
CLOUD_RUN_SERVICE Your Cloud Run service name

All secrets added

Step 7: Add the Dockerfile

Create a Dockerfile in your project root:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY server.js ./

# Cloud Run expects port 8080
EXPOSE 8080

# Start the server
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Clean, simple, and production-ready!

Step 8: Create the GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Deploy to Cloud Run

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    name: Deploy to Google Cloud Run
    runs-on: ubuntu-latest

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

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

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Configure Docker for Artifact Registry
        run: |
          gcloud auth configure-docker ${{ secrets.PROJECT_REGION }}-docker.pkg.dev

      - name: Build Docker image
        run: |
          docker build -t ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} .
          docker tag ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} \
                     ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:latest

      - name: Push Docker image to Artifact Registry
        run: |
          docker push ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }}
          docker push ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:latest

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy ${{ secrets.CLOUD_RUN_SERVICE }} \
            --image ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} \
            --platform managed \
            --region ${{ secrets.PROJECT_REGION }} \
            --project ${{ secrets.PROJECT_ID }} \
            --allow-unauthenticated \
            --min-instances 0 \
            --max-instances 10 \
            --memory 512Mi \
            --cpu 1 \
            --timeout 300 \
            --port 8080

      - name: Show deployment URL
        run: |
          echo "🚀 Deployment successful!"
          gcloud run services describe ${{ secrets.CLOUD_RUN_SERVICE }} \
            --platform managed \
            --region ${{ secrets.PROJECT_REGION }} \
            --project ${{ secrets.PROJECT_ID }} \
            --format 'value(status.url)'
Enter fullscreen mode Exit fullscreen mode

Step 9: Deploy!

Now for the moment of truth:

git add .
git commit -m "Initial Cloud Run deployment"
git push origin main
Enter fullscreen mode Exit fullscreen mode

Watch the magic happen in your GitHub Actions tab! In a few minutes, you'll have a live URL.

Your Project Structure

Here's what your repository should look like:

my-cloud-run-app/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── server.js
├── package.json
└── Dockerfile
Enter fullscreen mode Exit fullscreen mode

Testing Your Deployment

Once deployed, visit your Cloud Run URL (find it in the Actions output or Google Cloud Console):

curl https://your-service-url.run.app
# Returns: {"message":"Hello from Cloud Run!","timestamp":"2025-11-06T..."}

curl https://your-service-url.run.app/health
# Returns: {"status":"healthy"}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Tips

Build fails?

  • Check your Dockerfile syntax
  • Ensure package.json has all dependencies
  • Verify the Node version matches (we're using Node 20)

Deployment fails?

  • Verify all GitHub secrets are correct
  • Check the service account has the right permissions
  • Ensure regions match across all services

Container crashes?

  • Check Cloud Run logs in Google Cloud Console
  • Verify your app listens on port 8080
  • Ensure NODE_ENV is handled properly

Cost Awareness

Cloud Run has a generous free tier:

  • 2 million requests per month
  • 360,000 GB-seconds of memory
  • 180,000 vCPU-seconds

With min-instances: 0, you only pay when your app receives traffic. Perfect for side projects!

What's Next?

Now that you have a working deployment pipeline:

  1. Add environment variables: Use Google Secret Manager for API keys and secrets
  2. Add a custom domain: Make it look professional
  3. Set up monitoring: Cloud Run has built-in metrics
  4. Scale settings: Adjust min/max instances based on your needs
  5. Replace the demo: Swap in your actual application!

Level Up: Beyond Basic Services

Cloud Run isn't just for web services! Explore these deployment types:

  • Jobs: Perfect for batch processing, data pipelines, or scheduled tasks that run to completion
  • Worker Pools: Ideal for pull-based workloads like Kafka or Pub/Sub consumers

Conclusion

You now have a fully automated deployment pipeline! Every push to main triggers a build, test, and deploy cycle. Your app scales automatically, costs nothing when idle, and can handle massive traffic spikes.

The best part? This setup works for any Node.js app. Just replace our simple server with your actual application, adjust the Dockerfile if needed, and you're good to go!

Happy deploying! 🎊

Top comments (0)