Published on

FastAPI to Google Cloud Run: Prepare, Deploy, and Automate

Authors

Deploying a FastAPI application to Google Cloud Run can drastically simplify your infrastructure management. With a few steps, you can build a container, push it to Google Container Registry (GCR), and run it serverlessly on Cloud Run. In addition, you can automate deployments and updates seamlessly.

Follow along as we prepare, deploy, and then automate the process of shipping a FastAPI app to Google Cloud Run. Remember to keep sensitive environment variables secure. In the examples below, we will show placeholders for environment variables and redact them.

Requirements

  • Google Cloud SDK
  • Docker
  • FastAPI
  • Python
  • Prisma

Project Data

Setup Steps:

gcloud auth login

This command authenticates your Google Cloud SDK with your Google account. It opens a browser window where you can log in.

gcloud auth configure-docker

This command configures Docker to authenticate with Google Cloud’s Container Registry (GCR) using the credentials obtained from the gcloud auth login command. It allows Docker to push images to GCR securely.

gcloud config set project <PROJECT_ID>

This sets the active Google Cloud project to the specified <PROJECT_ID>. This ensures that subsequent commands are executed within the correct project context.

gcloud config set run/region <REGION>

This command configures the Google Cloud Run region. It sets the default region for Cloud Run services to the provided <REGION>, which will affect where your services are deployed.

gcloud services enable artifactregistry.googleapis.com --project=<PROJECT_ID>

Enables the Google Artifact Registry API for your project. Artifact Registry is a fully managed service for storing and managing Docker images and other artifacts.

gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="user:<YOUR_EMAIL>" \
--role="roles/run.admin"

This grants your user account (<YOUR_EMAIL>) the roles/run.admin role, which provides administrative access to manage Cloud Run services in the specified project.

gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="user:<YOUR_EMAIL>" \
--role="roles/iam.serviceAccountUser"

This grants your user account (<YOUR_EMAIL>) the roles/iam.serviceAccountUser role, which allows the user to use service accounts when interacting with Google Cloud resources.

(All env variables, including any database credentials or API keys, should be stored securely and never committed to the repository. In this example, we show placeholders only.)

Docker Build and Push

Build and push the image to GCR:

docker build -t gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG> -f production.Dockerfile .

This command builds a Docker image from the production.Dockerfile file and tags it as gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG>. The -t flag specifies the name and tag of the image, while -f points to the Dockerfile used for the build.

docker push gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG>

This command pushes the previously built Docker image to Google Container Registry, making it available for deployment.

gcloud run deploy <SERVICE_NAME> \
--image gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG> \
--platform managed \
--allow-unauthenticated \
--port=8000

Deploys the Docker image to Google Cloud Run, using the image stored in Google Container Registry. The --allow-unauthenticated flag allows public access to the service. The --platform managed option specifies the fully managed Cloud Run environment, and --port=8000 sets the port for the FastAPI app to listen on.

If you are on a Mac with an M1 chip, specify the platform:

docker build --platform=linux/amd64 -t gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG> .

This ensures that Docker builds the image for the linux/amd64 architecture, which is necessary on M1 Macs due to compatibility issues with certain architectures.

docker push gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG>

Pushes the image built for linux/amd64 to Google Container Registry.

gcloud run deploy <SERVICE_NAME> \
--image gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG> \
--platform managed \
--allow-unauthenticated \
--port=8000

Deploys the image to Cloud Run after it is pushed to Google Container Registry.

To adjust resource limits and set environment variables securely, do not hardcode them here. Instead, use placeholders and --set-env-vars with redacted keys:

gcloud run deploy <SERVICE_NAME> \
  --image gcr.io/<PROJECT_ID>/<SERVICE_NAME>:<TAG> \
  --platform managed \
  --allow-unauthenticated \
  --port=8000 \
  --memory=1Gi \
  --set-env-vars=DATABASE_URL="<REDACTED>",OPENAI_API_KEY="<REDACTED>",RESEND_API_KEY="<REDACTED>",FROM_EMAIL="<REDACTED>",EXOSCALE_ACCESS_KEY="<REDACTED>",EXOSCALE_SECRET_KEY="<REDACTED>",EXOSCALE_S3_ENDPOINT="<REDACTED>",EXOSCALE_S3_REGION="<REDACTED>",EXOSCALE_S3_BUCKET_NAME="<REDACTED>",APPLICATION_URL="<REDACTED>",JWT_SECRET="<REDACTED>"

This command deploys the application to Cloud Run with additional configuration like memory allocation (--memory=1Gi) and securely passing environment variables using --set-env-vars. Environment variables are used for sensitive configuration, and the placeholders <REDACTED> should be replaced with actual, securely stored values.

Dockerfile for Production Deployment

# Base image
FROM python:3.11-slim

This sets the base image for the Dockerfile. It uses a minimal Python image (python:3.11-slim), which is optimized for production environments.

RUN apt-get update && apt-get install -y \
    libpq-dev \
    gcc \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

Installs system dependencies required for PostgreSQL (libpq-dev) and gcc for compiling certain Python packages.

WORKDIR /usr/src/app

Sets the working directory within the container where your application code will be placed.

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

Sets environment variables to ensure Python runs in an optimized way for production (disabling bytecode files and buffering).

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

Copies the requirements.txt file into the container and installs the Python dependencies listed in it.

COPY app ./app
COPY prisma ./prisma

Copies the application files (app) and the Prisma schema (prisma) into the container.

EXPOSE 8000

Exposes port 8000 to allow the application to be accessed via that port.

COPY entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh

Copies the entrypoint.sh script into the container and makes it executable. The script is used to start the application.

CMD ["/usr/src/app/entrypoint.sh"]

Specifies the command to run when the container starts. This runs the entrypoint.sh script to start the application.

entrypoint.sh:

#!/bin/sh
prisma generate --schema ./prisma/schema.prisma
uvicorn app.main:app --host 0.0.0.0 --port 8000

This script runs the prisma generate command to generate Prisma client code and starts the FastAPI app using uvicorn.

Automate Deployment with deployment.sh

The deployment.sh script simplifies the deployment process for your FastAPI application. It ensures that environment variables are loaded securely from a .env file, validates the tag format (e.g., v0.0.0), and automates all deployment steps:

  • Builds a Docker image optimized for the correct architecture (e.g., linux/amd64).
  • Pushes the image to Google Container Registry (GCR).
  • Deploys the FastAPI application to Google Cloud Run using the specified image tag, passing all environment variables dynamically.

Here is an example script:

#!/usr/bin/env bash
set -e

if [ -z "$1" ]; then
    echo "Usage: $0 <tag>"
    exit 1
fi

This script starts with a basic setup. It checks if a tag is passed as an argument; if not, it exits with an error message.

TAG="$1"

This stores the tag passed as the first argument into the TAG variable.

if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Tag must follow the format v0.0.0"
    exit 1
fi

This checks if the tag matches the v0.0.0 format using a regular expression. If it doesn't, it exits the script with an error message.

if [ ! -f .env ]; then
    echo ".env file not found"
    exit 1
fi

Checks if the .env file exists, which contains the environment variables. If not, it exits with an error message.

export $(grep -v '^#' .env | xargs)

This loads the environment variables from the .env file into the shell, ignoring commented lines.

ENV_VARS=$(grep -v '^#' .env | grep '=' | paste -sd ',' -)

This formats the environment variables in the .env file into a single comma-separated string suitable for passing to gcloud.

docker build --platform=linux/amd64 -t gcr.io/<PROJECT_ID>/fastapi-template:${TAG} -f production.Dockerfile .
docker push gcr.io/<PROJECT_ID>/fastapi-template:${TAG}

Builds and pushes the Docker image as described earlier, using the provided tag.

gcloud run deploy fastapi-template \
  --image gcr.io/<PROJECT_ID>/fastapi-template:${TAG} \
  --platform managed \
  --allow-unauthenticated \
  --port=8000 \
  --memory=1Gi \
  --set-env-vars=${ENV_VARS}

Deploys the image to Cloud Run, passing the environment variables dynamically and setting resource limits.

To deploy a new version, simply run the script with the desired tag, for example:

./deployment.sh v0.0.1

This command runs the script to deploy a new version of the application.

Conclusion

By following these steps, you can seamlessly set up your FastAPI application to run on Google Cloud Run. The included deployment.sh script further simplifies deployments, making it easy to ship updates reliably and securely. Always remember to manage your environment variables securely and avoid hardcoding sensitive information.

Happy Deploying!

Files

#!/usr/bin/env bash
set -e

if [ -z "$1" ]; then
    echo "Usage: $0 <tag>"
    exit 1
fi

TAG="$1"

# Check if tag matches v0.0.0 format
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Tag must follow the format v0.0.0"
    exit 1
fi

# Check if .env file exists
if [ ! -f .env ]; then
    echo ".env file not found"
    exit 1
fi

# Load environment variables from .env file
export $(grep -v '^#' .env | xargs)

# Build the --set-env-vars argument from the .env file
ENV_VARS=$(grep -v '^#' .env | grep '=' | paste -sd ',' -)

docker build --platform=linux/amd64 -t gcr.io/saas-template-439404/fastapi-template:${TAG} -f production.Dockerfile .
docker push gcr.io/saas-template-439404/fastapi-template:${TAG}

gcloud run deploy fastapi-template \
  --image gcr.io/saas-template-439404/fastapi-template:${TAG} \
  --platform managed \
  --allow-unauthenticated \
  --port=8000 \
  --memory=1Gi \
  --set-env-vars=${ENV_VARS}

# Base image
FROM python:3.11-slim

# Install PostgreSQL development files and system dependencies
RUN apt-get update && apt-get install -y \
    libpq-dev \
    gcc \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /usr/src/app

# Set environment variables for FastAPI production
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application files
COPY app ./app
COPY prisma ./prisma
# Expose the application port
EXPOSE 8000

COPY entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh

EXPOSE 8000
CMD ["/usr/src/app/entrypoint.sh"]