How I Set Up Cloud Run CI/CD in Under 30 Minutes for the Cloud Run Hackathon
This post was created for the Cloud Run Hackathon to document my deployment setup experience.
When I decided to participate in the Cloud Run Hackathon, one of my biggest concerns was getting lost in deployment configuration. I needed a reliable, automated deployment pipeline that wouldn't eat into my development time. Following this structured approach helped me get everything running in under 30 minutes with my FastAPI application.
Why This Mattered for the Hackathon
The hackathon rewards bonus points for using Cloud Run effectively, especially when running multiple services (frontend and backend). Setting up a proper CI/CD pipeline early meant I could focus on building my actual application instead of wrestling with deployment issues every time I made a change.
What I Built On
I started with a basic FastAPI server as a foundation. FastAPI's speed and automatic API documentation made it perfect for rapid development during the hackathon. Here's the minimal setup that got me started:
main.py - A basic health check endpoint and main route:
from fastapi import FastAPI
from datetime import datetime
import os
app = FastAPI()
@app.get("/")
async def root():
return {
"message": "Hello from Cloud Run!",
"timestamp": datetime.utcnow().isoformat(),
"environment": os.getenv("ENV", "development")
}
@app.get("/health")
async def health():
return {"status": "healthy"}
requirements.txt - Minimal dependencies:
fastapi==0.104.1
uvicorn[standard]==0.24.0
The 30-Minute Setup Process
Phase 1: Google Cloud Project Setup (10 minutes)
I headed to the Google Cloud Console and created a new project. The key was keeping track of every identifier I created - Project ID, region selection, service names. I chose europe-west1 as my region and stuck with it consistently.
Creating a new project:
I gave it a memorable name and saved the Project ID for later use.
Phase 2: Setting Up Cloud Run (5 minutes)
I searched for "Cloud Run" in the console and clicked "Deploy Container":
Configuration was straightforward:
- Service name:
fastapi-server - Region:
europe-west1 - Authentication: Public access for testing
- Container image URL: Left empty (GitHub Actions would handle this)
The missing container image error was expected at this stage.
Phase 3: Artifact Registry Configuration (5 minutes)
I created a Docker repository in Artifact Registry:
Configuration:
- Format: Docker
- Name:
my-app-repo - Region: Same as Cloud Run (
europe-west1) - Mode: Standard
Phase 4: Service Account and Permissions (8 minutes)
This was critical. I created a service account that would give GitHub permission to deploy on my behalf.
I named it github-deployer and assigned four specific roles:
| Role | Purpose |
|---|---|
| Artifact Registry Reader | Allows Cloud Run to fetch images |
| Artifact Registry Writer | Allows GitHub Actions to push builds |
| Cloud Run Admin | Enables deployment updates |
| Service Account User | Lets Cloud Run execute containers |
Then I generated a JSON key:
This JSON file is sensitive - it grants deployment permissions.
Phase 5: GitHub Secrets Configuration (5 minutes)
I configured GitHub repository secrets under Settings → Secrets and variables → Actions:
I added five secrets:
| Secret Name | Value |
|---|---|
GCP_SA_KEY |
Entire JSON key file content |
PROJECT_ID |
My Google Cloud project ID |
PROJECT_REGION |
europe-west1 |
ARTIFACT_REGISTRY_REPO |
my-app-repo |
CLOUD_RUN_SERVICE |
fastapi-server |
Phase 6: Dockerfile and Workflow (2 minutes)
The Dockerfile was optimized for FastAPI:
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY main.py .
# Cloud Run expects port 8080
EXPOSE 8080
# Start uvicorn server
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
The GitHub Actions workflow in .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)'
First Deployment
I committed everything and pushed to main:
git add .
git commit -m "Initial Cloud Run deployment"
git push origin main
The GitHub Actions workflow executed automatically. Within 3-4 minutes, I had a live deployment URL with FastAPI's automatic interactive API documentation available at /docs.
Final Project Structure
my-cloud-run-app/
├── .github/
│ └── workflows/
│ └── deploy.yml
├── main.py
├── requirements.txt
└── Dockerfile
Testing the Deployment
Once deployed, I could access:
# Main endpoint
curl https://your-service-url.run.app
# Returns: {"message":"Hello from Cloud Run!","timestamp":"2025-11-09T...","environment":"development"}
# Health check
curl https://your-service-url.run.app/health
# Returns: {"status":"healthy"}
# Interactive API docs (browser)
https://your-service-url.run.app/docs
FastAPI's automatic OpenAPI documentation was a huge win during the hackathon - I could test endpoints directly from the browser.
How This Helped My Hackathon Project
With this foundation in place, I could:
- Iterate quickly: Every push to main automatically deployed
- Test in production: Real Cloud Run environment from day one
- Scale easily: Adding a second service (frontend) followed the same pattern
-
Debug efficiently: FastAPI's detailed error messages and
/docsendpoint - Focus on features: No more deployment anxiety
Adapting for Multiple Services
For the hackathon's bonus points, I needed both frontend and backend services. The pipeline made this straightforward:
- Created a separate frontend repository
- Set up another Cloud Run service with a similar workflow
- Connected services using Cloud Run's internal networking
- Both services deployed automatically on their respective branches
- Used CORS middleware in FastAPI to handle cross-origin requests:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Updated with actual frontend URL in production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Cost Considerations
With min-instances: 0, the services scaled to zero when not in use. Cloud Run's free tier covers:
- 2 million requests per month
- 360,000 GB-seconds of memory
- 180,000 vCPU-seconds
Perfect for hackathon projects with minimal costs during development.
Key Takeaways
What worked well:
- FastAPI's speed made iteration rapid
- Having all configuration in one place (GitHub secrets)
- Using standard Docker practices
- Automated deployment removed manual errors
- Service account permissions were clearly defined
- Built-in API documentation at
/docsfor testing
Common pitfalls I avoided:
- Mismatched regions across services
- Forgetting to save the Project ID early
- Not testing the health endpoint before deploying
- Exposing the wrong port (always use 8080 for Cloud Run)
- Not installing uvicorn with the
[standard]extras for production
FastAPI-specific considerations:
- Using
uvicorninstead of development server - Setting
--host 0.0.0.0to accept external connections - Including CORS middleware for frontend integration
Beyond the Basic Setup
Once the pipeline was running, I expanded my hackathon project with:
- Environment variables via Google Secret Manager for API keys
- Cloud Run Jobs for background processing tasks
- Pub/Sub integration for event-driven workflows
- Gemini API integration for AI features (bonus points)
- FastAPI's dependency injection for database connections
- Pydantic models for request/response validation
Performance Notes
FastAPI on Cloud Run performed excellently:
- Cold start times: ~2-3 seconds with the slim Python image
- Request latency: Sub-100ms for most endpoints
- The async/await support in FastAPI worked seamlessly
- Auto-scaling handled traffic spikes without issues
Troubleshooting Tips
Build fails?
- Check your Dockerfile syntax
- Ensure
requirements.txthas all dependencies - Verify the Python version matches (using Python 3.11)
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 environment variables are handled properly
Conclusion
This 30-minute setup gave me a production-ready deployment pipeline that handled the infrastructure concerns, letting me focus on building my hackathon submission. The combination of GitHub Actions and Cloud Run meant I could iterate rapidly without deployment friction.
FastAPI was the perfect choice for the backend - its speed, automatic documentation, and type safety accelerated development significantly. The deployment pipeline ensured that every feature I built was live within minutes.
For anyone participating in the Cloud Run Hackathon, I recommend setting up this pipeline first. Get your deployment working, then build your features. The automation pays for itself immediately.
The complete code structure and workflow are ready to adapt for any FastAPI application. Just replace the simple server with your actual application logic, add your models and endpoints, adjust resource limits if needed, and deploy.
This post was created to document my Cloud Run deployment experience for the Cloud Run Hackathon.


















Top comments (0)