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


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
- Overview & Architecture
- Prerequisites
- Store Environment Variables in Secret Manager
- Configure Service Accounts & Permissions
- Build & Push Docker Images to GCR
- Automate with GitHub Actions
- Deploy to Cloud Run
- Troubleshooting & Best Practices
- 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:
- Push to GitHub triggers GitHub Actions workflow
- Build Docker image
- Push image to GCR
- Deploy to Cloud Run, referencing secrets from Secret Manager
- 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 ofcreate
.
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.
Create the Service Account:
gcloud iam service-accounts create github-actions --display-name "GitHub Actions Deployments"
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.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
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
- Name:
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:
Addroles/artifactregistry.writer
to your service account.repo does not exist. Creating on push requires ...
Addroles/artifactregistry.createOnPushRepoAdmin
or manually create the repo.The following reserved env names were provided: PORT
RemovePORT
from--set-env-vars
(Cloud Run sets it).Docker CMD warning:
Use a JSON array forCMD
in your Dockerfile.Secrets not available?
Ensure correct secret names and versions, and that Cloud Run’s service account hasroles/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!
Reference Links
- Google Cloud Run Documentation
- Google Secret Manager Documentation
- GitHub Actions for Google Cloud
- Best Practices for CI/CD on GCP
Happy deploying! 🚀
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