When I built ScriptForge — a multi-agent AI content automation platform — I wanted every push to main to automatically lint, build, containerize, and deploy without me touching a single command manually.
This is exactly how I did it.
What We're Building
A 4-stage GitHub Actions pipeline that:
Checks frontend code — build + type check
Checks backend code — lint + type check
Builds and pushes a Docker image to Google Artifact Registry
Deploys automatically to Google Cloud Run
Every time you push to main, all 4 stages run in sequence. If any stage fails, the deploy stops. If all pass, your new version is live within minutes — zero manual steps.
The Stack
Frontend: React + TypeScript (Vite)
Backend: Python + FastAPI
Containerization: Docker
CI/CD: GitHub Actions
Registry: Google Artifact Registry
Hosting: Google Cloud Run
Step 1 — Dockerize Your Backend
First, your FastAPI backend needs a Dockerfile:
dockerfileFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
A few things worth noting:
Cloud Run expects your container to listen on port 8080
Use python:3.11-slim to keep the image small and fast to build
Always copy requirements.txt before your source code — Docker caches layers, so dependencies only reinstall when requirements.txt changes
Step 2 — Set Up Google Cloud
You need three things configured in GCP before GitHub Actions can deploy:
- Enable the required APIs bashgcloud services enable run.googleapis.com gcloud services enable artifactregistry.googleapis.com
- Create an Artifact Registry repository bashgcloud artifacts repositories create your-repo-name \ --repository-format=docker \ --location=us-central1
- Create a Service Account for GitHub Actions bashgcloud iam service-accounts create github-actions-deployer \ --display-name="GitHub Actions Deployer" Grant it the minimum permissions needed: bash# Push images to Artifact Registry gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ --member="serviceAccount:github-actions-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/artifactregistry.writer"
Deploy to Cloud Run
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:github-actions-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/run.admin"
Act as the Cloud Run service account
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:github-actions-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
Then export the key and save it as a GitHub secret:
bashgcloud iam service-accounts keys create key.json \
--iam-account=github-actions-deployer@YOUR_PROJECT_ID.iam.gserviceaccount.com
Step 3 — Add GitHub Secrets
In your GitHub repo go to Settings → Secrets and variables → Actions and add:
SecretValueGCP_SA_KEYContents of key.jsonGCP_PROJECT_IDYour Google Cloud project ID
Never commit key.json to your repo. Add it to .gitignore immediately after creating it.
Step 4 — The GitHub Actions Workflow
Create .github/workflows/ci.yml:
yamlname: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
frontend-check:
name: Frontend build & type check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: frontend
- name: Type check
run: npx tsc --noEmit
working-directory: frontend
- name: Build
run: npm run build
working-directory: frontend
backend-check:
name: Backend lint & type check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
working-directory: backend
- name: Lint with ruff
run: pip install ruff && ruff check .
working-directory: backend
docker-build-push:
name: Docker build & push
runs-on: ubuntu-latest
needs: [frontend-check, backend-check]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev
- name: Build and push Docker image
run: |
docker build -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/your-repo-name/backend:${{ github.sha }} ./backend
docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/your-repo-name/backend:${{ github.sha }}
deploy:
name: Deploy to Google Cloud Run
runs-on: ubuntu-latest
needs: docker-build-push
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: your-service-name
region: us-central1
image: us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/your-repo-name/backend:${{ github.sha }}
How the Pipeline Works
Push to main
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Frontend check │ │ Backend check │
│ build + tsc │ │ lint + types │
└────────┬────────┘ └────────┬────────┘
│ │
└──────────┬────────────┘
│ both pass
▼
┌─────────────────────┐
│ Docker build+push │
│ → Artifact Registry│
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Deploy to │
│ Google Cloud Run │
└─────────────────────┘
│
▼
🚀 Live in ~2min
The needs keyword is key here — Docker build only runs if both checks pass. Deploy only runs if Docker push succeeds. One failure stops the whole chain.
What This Gives You
Before this pipeline, deploying looked like this:
Build the Docker image locally
Tag it
Authenticate to GCP
Push the image
Open Cloud Console
Trigger a new revision
Wait and check logs manually
After this pipeline, deploying looks like this:
git push origin main
That's it. The pipeline handles everything else. Every commit is tested before it ships. Every deploy is traceable to a specific commit SHA. You never push broken code to production again.
One Thing I'd Do Differently
I'd add environment-specific secrets earlier. Managing staging vs production Cloud Run services from the start — deploying main to staging automatically and requiring a manual approval for production — is worth setting up before your app has real users, not after.
Wrapping Up
This exact pipeline runs on ScriptForge in production today. Four jobs, fully automated, zero manual deployment steps. If you're still deploying by hand, this setup takes an afternoon to configure and saves you that time on every single deploy going forward.
The full ScriptForge repo is on GitHub if you want to see the complete workflow file in context.
Built by Izehiuwa Igiebor Omogiate — Full Stack Developer based in São Paulo, Brazil.
GitHub · LinkedIn · Portfolio
Top comments (0)