How to Build and Deploy a Flask API to Google Cloud Run with GitHub Actions & Secret Manager

Uttam MahataUttam Mahata
5 min read

In this post, you’ll learn how to take your Flask application from development to production using Google Cloud Run as your serverless platform, storing secrets in Google Cloud Secret Manager, and automating your build-and-deploy process using GitHub Actions. This is a real-world, production-grade CI/CD setup that’s secure, reproducible, and ready for scale.


Table of Contents

  1. Overview & Architecture
  2. Prerequisites
  3. Store Environment Variables in Secret Manager
  4. Configure Service Accounts & Permissions
  5. Build & Push Docker Images to GCR
  6. Automate with GitHub Actions
  7. Deploy to Cloud Run
  8. Troubleshooting & Best Practices
  9. Final Thoughts

Overview & Architecture

Goal:
Automate building, securing, and deploying a Flask REST API to Google Cloud Run using a GitHub Actions pipeline, with all secrets securely managed in Google Cloud Secret Manager.

Key Components:

  • Flask API: Your application
  • Docker: Containerize the app
  • Google Cloud Run: Serverless deployment
  • Google Container Registry (GCR): Store Docker images
  • Google Secret Manager: Securely store environment secrets
  • GitHub Actions: Automate build & deploy

Workflow:

  1. Push to GitHub triggers GitHub Actions workflow
  2. Build Docker image
  3. Push image to GCR
  4. Deploy to Cloud Run, referencing secrets from Secret Manager
  5. Cloud Run service is live and securely configured

Prerequisites

  • Google Cloud Project (e.g. alphaaiapis)
  • Google Cloud SDK installed locally
  • GitHub repository for your code
  • Billing enabled on GCP project
  • Basic knowledge of Docker, Python, and GCP

Store Environment Variables in Secret Manager

First, never store production secrets in plaintext or directly in your repo. Use Secret Manager for secure storage.

Suppose your .env file contains:

SQLALCHEMY_DATABASE_URI=...
JWT_SECRET_KEY=...
MISTRAL_API_KEY=...
GEMINI_API_KEY=...
GOOGLE_API_KEY=...
GOOGLE_MAPS_API_KEY=...
OPENWEATHER_API_KEY=...
FLASK_ENV=production

To upload each as a secret:

gcloud config set project alphaaiapis
for secret in $(cat .env | grep -v '^#' | cut -d= -f1); do
  gcloud secrets create $secret --replication-policy="automatic" --data-file=<(grep "^$secret=" .env | cut -d= -f2-)
done

For existing secrets use gcloud secrets versions add ... instead of create.

Grant Access to Cloud Run Service Account

Find your Cloud Run service account (example: 123456789049-compute@developer.gserviceaccount.com) and grant Secret Accessor role:

gcloud secrets add-iam-policy-binding SECRET_NAME \
  --member serviceAccount:YOUR_CLOUD_RUN_SA \
  --role roles/secretmanager.secretAccessor

Configure Service Accounts & Permissions

Create a Service Account for GitHub Actions

This service account will be used for CI/CD deployments from GitHub.

  1. Create the Service Account:

     gcloud iam service-accounts create github-actions --display-name "GitHub Actions Deployments"
    
  2. Assign Required Roles:

     gcloud projects add-iam-policy-binding alphaaiapis \
       --member="serviceAccount:github-actions@alphaaiapis.iam.gserviceaccount.com" \
       --role="roles/run.admin"
     gcloud projects add-iam-policy-binding alphaaiapis \
       --member="serviceAccount:github-actions@alphaaiapis.iam.gserviceaccount.com" \
       --role="roles/artifactregistry.writer"
     gcloud projects add-iam-policy-binding alphaaiapis \
       --member="serviceAccount:github-actions@alphaaiapis.iam.gserviceaccount.com" \
       --role="roles/iam.serviceAccountUser"
     gcloud projects add-iam-policy-binding alphaaiapis \
       --member="serviceAccount:github-actions@alphaaiapis.iam.gserviceaccount.com" \
       --role="roles/secretmanager.secretAccessor"
    

    Add roles/artifactregistry.createOnPushRepoAdmin if you want to allow the pipeline to auto-create new repositories.

  3. Create and Download a Service Account Key:

     gcloud iam service-accounts keys create github-actions-key.json \
       --iam-account=github-actions@alphaaiapis.iam.gserviceaccount.com
    
  4. Add the Service Account Key to GitHub Secrets:

    • Go to your GitHub repo → Settings → Secrets and variables → Actions
    • New secret:
      • Name: GCP_SA_KEY
      • Value: Paste the entire JSON from github-actions-key.json

Build & Push Docker Images to GCR

Dockerfile Example

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

COPY . .

# Use JSON array for CMD to avoid signal issues
CMD ["sh", "-c", "exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 run:app"]

.dockerignore Example

__pycache__/
*.pyc
.env
.github/

Automate with GitHub Actions

Create the workflow file: .github/workflows/deploy.yml

name: Build and Deploy to Cloud Run

on:
  push:
    branches:
      - main
      - 'release/**'

env:
  PROJECT_ID: alphaaiapis
  SERVICE: alphaai-backend
  REGION: us-central1

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

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

      - name: Set up Google Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
        with:
          project_id: ${{ env.PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      - name: Configure Docker for Google Cloud
        run: gcloud auth configure-docker

      - name: Build Docker image
        run: |
          docker build -t gcr.io/$PROJECT_ID/$SERVICE:${{ github.sha }} .

      - name: Push Docker image
        run: |
          docker push gcr.io/$PROJECT_ID/$SERVICE:${{ github.sha }}

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy $SERVICE \
            --image gcr.io/$PROJECT_ID/$SERVICE:${{ github.sha }} \
            --platform managed \
            --region $REGION \
            --allow-unauthenticated \
            --port 8080 \
            --memory 2Gi \
            --cpu 1 \
            --max-instances 10 \
            --min-instances 0 \
            --timeout 3600 \
            --concurrency 80 \
            --set-env-vars "FLASK_APP=run.py" \
            --set-secrets "SQLALCHEMY_DATABASE_URI=SQLALCHEMY-DATABASE-URI:latest,JWT_SECRET_KEY=JWT-SECRET-KEY:latest,MISTRAL_API_KEY=MISTRAL-API-KEY:latest,GEMINI_API_KEY=GEMINI-API-KEY:latest,GOOGLE_API_KEY=GOOGLE-API-KEY:latest,GOOGLE_MAPS_API_KEY=GOOGLE-MAPS-API-KEY:latest,OPENWEATHER_API_KEY=OPENWEATHER-API-KEY:latest,FLASK_ENV=FLASK-ENV:latest"

      - name: Test health endpoint
        run: |
          curl --fail "https://$SERVICE-$REGION.a.run.app/api/health"

Note:

  • The PORT environment variable is reserved by Cloud Run and is set automatically—do not include it in --set-env-vars.
  • If you see errors about missing repositories, make sure your service account has permissions to create them, or create the repo manually.

Deploy to Cloud Run

After a successful GitHub Actions run:

  • Your image is pushed to GCR
  • Your Cloud Run service is updated with the new image
  • All environment variables are securely loaded as secrets

Test your health endpoint (example):

curl https://alphaai-backend-us-central1.a.run.app/api/health

Troubleshooting & Best Practices

Common Errors & Fixes

  • Permission denied pushing to GCR:
    Add roles/artifactregistry.writer to your service account.

  • repo does not exist. Creating on push requires ...
    Add roles/artifactregistry.createOnPushRepoAdmin or manually create the repo.

  • The following reserved env names were provided: PORT
    Remove PORT from --set-env-vars (Cloud Run sets it).

  • Docker CMD warning:
    Use a JSON array for CMD in your Dockerfile.

  • Secrets not available?
    Ensure correct secret names and versions, and that Cloud Run’s service account has roles/secretmanager.secretAccessor.

Security & Best Practices

  • Never commit your .env or service account keys to the repo
  • Use Workload Identity Federation for even better security (advanced)
  • Regularly rotate your secret keys and service account credentials
  • Use separate environments (dev/prod) and configure separate secrets

Final Thoughts

With this setup, you have:

  • Secure, automated CI/CD from GitHub to Cloud Run
  • Secrets managed with GCP Secret Manager
  • Production Flask app served cost-effectively and scalably

Next steps:

  • Add tests to your GitHub Actions workflow
  • Monitor deployments and logs in the GCP Console
  • Scale up with more services or blueprints as your app grows!


Happy deploying! 🚀

0
Subscribe to my newsletter

Read articles from Uttam Mahata directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Uttam Mahata
Uttam Mahata

An undergraduate student pursuing a Bachelor's degree in Computer Science and Technology at the Indian Institute of Engineering Science and Technology, Shibpur, I have developed a deep interest in data science, machine learning, and web development. I am actively seeking internship opportunities to gain hands-on experience and apply my skills in a formal, professional setting. Programming Languages: C/C++, Java, Python Web Development: HTML, CSS, Angular, JavaScript, TypeScript, PrimeNG, Bootstrap Technical Skills: Data Structures, Algorithms, Object-Oriented Programming, Data Science, MySQL, SpringBoot Version Control : Git Technical Interests: Data Science, Machine Learning, Web Development