Tired of manually building and deploying your FastAPI app every time you make a change? There's a much smoother way using Google Cloud. Let's set up a simple Continuous Integration and Continuous Deployment (CI/CD) pipeline.
When you push code changes to your GitHub repository, this pipeline will automatically build a container image and deploy it to Google Cloud Run. We'll use Google Cloud Build to handle the building and pushing, and Cloud Run to run our container without worrying about servers.
This means faster updates, fewer mistakes, and more time for you to focus on writing code.
First things first, you need to make sure Cloud Build is allowed to run in your GCP project.
You need two key files in the root of your GitHub repository: Dockerfile and cloudbuild.yaml.
This file tells Docker how to package your FastAPI application. Here's a sample one. Important: Replace "my-project" in the WORKDIR, COPY, and RUN commands below with the actual name of your application's directory or desired work directory name within the container.
# Use a specific Python version. Slim-buster is a good lightweight choice. FROM python:3.11-slim-buster # Set the working directory inside the container WORKDIR /my-project # Install system packages needed (like ffmpeg if your app uses it, gcc for some Python packages) # Update package lists, install, then clean up to keep the image small RUN apt-get update && \ apt-get install -y \ gcc \ ffmpeg \ && rm -rf /var/lib/apt/lists/* # Copy just the requirements file first. Docker caches this layer. # If requirements.txt doesn't change, Docker reuses the cached layer, speeding up builds. COPY ./requirements.txt /my-project/requirements.txt # Install Python dependencies RUN pip install --no-cache-dir --upgrade -r /my-project/requirements.txt # Copy the rest of your application code # Assumes your FastAPI code is in a directory named 'app' COPY ./app /my-project/app # Command to run your application when the container starts # Uses gunicorn as the web server, binding to the port provided by Cloud Run ($PORT) # Adjust workers/threads based on your app's needs and Cloud Run instance size CMD exec gunicorn --bind :$PORT --workers 4 --worker-class uvicorn.workers.UvicornWorker --threads 4 -t 60 --max-requests 1000 --max-requests-jitter 50 app.main:app
This file tells Cloud Build the steps to take: build the image, push it to Google Container Registry (GCR), and deploy it to Cloud Run.
Important: Replace "my-project" in the args for building, pushing, and deploying with the exact same name you want for your Cloud Run service and container image path.
steps: # Step 1: Build the Docker image # Uses the standard Docker builder provided by Google Cloud Build # Tags the image with the unique commit SHA for easy tracking - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-project:$COMMIT_SHA', '.'] # Step 2: Push the built image to Google Container Registry (GCR) # GCR is Google's private Docker image storage - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/my-project:$COMMIT_SHA'] # Step 3: Deploy the image to Cloud Run # Uses the gcloud command-line tool (also provided as a builder) # Deploys a service named "my-project" using the image we just pushed # Sets an environment variable (e.g., ENV=LIVE) # Specifies the region and project - name: 'gcr.io/cloud-builders/gcloud' args: - 'run' - 'deploy' - 'my-project' # This should be your service name - '--image=gcr.io/$PROJECT_ID/my-project:$COMMIT_SHA' # Use the image we built/pushed - '--set-env-vars=ENV=LIVE' # Example environment variable - '--region=us-central1' # Choose the region for your service - '--project=$PROJECT_ID' # Add other flags as needed: --memory, --cpu, --allow-unauthenticated, etc. # Step 4: Make sure 100% of traffic goes to the new version # This ensures the update is live immediately - name: 'gcr.io/cloud-builders/gcloud' args: - 'run' - 'services' - 'update-traffic' - 'my-project' # Your service name again - '--to-latest' - '--region=us-central1' # Same region as deployment - '--project=$PROJECT_ID' # Configure Cloud Build options options: # Send build logs straight to Cloud Logging logging: CLOUD_LOGGING_ONLY
Make sure both these files (Dockerfile and cloudbuild.yaml) are committed and pushed to the root of your GitHub repository.
Now, let's connect Cloud Build to your GitHub repository.
That's it for setup!
Now, whenever you push a commit to the specific branch (main in our example) of your connected GitHub repository:
A significant improvement to both your Docker image size and Cloud Run cold start times can be achieved by implementing a multi-stage Docker build. Even when starting with a slim
base image, a multi-stage build can reduce the final image size by more than 30%. This is because it separates the build environment (with all its dependencies like compilers and build tools) from the final runtime environment, which only contains the necessary components to run your application.
Benefits:
Here's an example of an updated Dockerfile
using a multi-stage approach:
# Stage 1: Builder # This stage installs build dependencies and Python packages FROM python:3.13-slim AS builder WORKDIR /opt/app_build # Install OS build dependencies required for some Python packages (e.g., gcc for C extensions, libffi-dev) RUN apt-get update && \ apt-get install -y --no-install-recommends \ gcc \ libffi-dev \ && rm -rf /var/lib/apt/lists/* # Copy requirements file COPY ./requirements.txt . # Create a virtual environment and install Python dependencies into it # Using a venv helps keep dependencies isolated and makes the copy to the next stage cleaner RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" RUN pip install --no-cache-dir --upgrade -r requirements.txt # Stage 2: Final image # This stage builds the final, lean image with only runtime necessities FROM python:3.13-slim WORKDIR /katanasa-app # Install minimal runtime OS dependencies if absolutely necessary # For a basic FastAPI app, you might not need many extra OS packages here. # The example below includes some common ones for PDF/image generation, but adjust as needed. # RUN apt-get update && \ # apt-get install -y --no-install-recommends \ # libglib2.0-0 \ # # Add other essential runtime libs only if your app strictly requires them # && rm -rf /var/lib/apt/lists/* # Copy the virtual environment (with installed packages) from the builder stage COPY --from=builder /opt/venv /opt/venv # Add the virtual environment's bin directory to the PATH for the runtime stage ENV PATH="/opt/venv/bin:$PATH" # Copy your application code COPY ./app /katanasa-app/app # Set environment variable for Python (good practice for running in containers) ENV PYTHONUNBUFFERED=1 # Command to run your application when the container starts # Uses gunicorn as the web server, binding to the port provided by Cloud Run ($PORT) CMD exec gunicorn --bind :$PORT --workers 1 --worker-class uvicorn.workers.UvicornWorker --threads 4 app.main:app
By adopting a multi-stage build, you ensure your production container is as small and efficient as possible, directly contributing to better performance and potentially lower costs on Cloud Run.
Enjoyed this blog post? Check out these related posts!
Reflex Makes SEO Easier: Automatic robots.txt and sitemap.xml Generation
Discover how adding your deploy URL in Reflex automatically generates robots.txt and sitemap.xml for easier SEO.
Read More..
Optimizing Reflex Performance on Google Cloud Run
A Comparison of Gunicorn, Uvicorn, and Granian for Running Reflex Apps
Read More..
Adding Google Authentication to Your FastAPI Application
A guide to adding Google Authentication to your FastAPI app.
Read More..
Have a project in mind? Send me an email at hello@davidmuraya.com and let's bring your ideas to life. I am always available for exciting discussions.