DEV Community

Cover image for Serverless Python: Deploying FastAPI to Google Cloud Run with Docker
Ayush Kumar
Ayush Kumar

Posted on • Originally published at logiclooptech.dev

Serverless Python: Deploying FastAPI to Google Cloud Run with Docker

Introduction

In my previous article, we built a high-performance Async API using FastAPI and SQLAlchemy 2.0.

But code that only lives on localhost provides zero value.

Today, we are moving that API to production. We will containerize our application using Docker and deploy it to Google Cloud Run - a fully managed serverless platform that scales automatically.

Why Cloud Run?

  • Scale to Zero: You don't pay for servers when no one is using your API.

  • Concurrency: Unlike AWS Lambda (which handles 1 request per instance), Cloud Run can handle 80+ requests per container concurrently.

  • Portability: Since it's just a Docker container, you can move it to AWS or Azure later if you want.


Step 1: The Dockerfile

The first step is packaging our Python environment. Create a file named Dockerfile (no extension) in your project root.

We will use a multi-stage build or a slim image to keep our container lightweight.

Dockerfile

# 1. Use an official lightweight Python image
FROM python:3.11-slim

# 2. Set environment variables
# PYTHONDONTWRITEBYTECODE: Prevents Python from writing .pyc files
# PYTHONUNBUFFERED: Ensures logs are flushed directly to terminal (critical for Cloud Logging)
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 3. Set the working directory
WORKDIR /app

# 4. Install dependencies
# We copy requirements.txt first to leverage Docker cache
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 5. Copy the application code
COPY . .

# 6. Expose the port (Cloud Run defaults to 8080)
EXPOSE 8080

# 7. Run the application
# Host 0.0.0.0 is required for the container to be accessible from outside
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Make sure you have a requirements.txt file. You can generate one with pip freeze > requirements.txt.


Step 2: Build & Test Locally

Before pushing to the cloud, always test the container locally.

Bash

# Build the image
docker build -t logicloop-api .

# Run the container
docker run -p 8080:8080 logicloop-api
Enter fullscreen mode Exit fullscreen mode

Go to http://localhost:8080/docs. If you see your Swagger UI, your container is valid.


Step 3: Push to Google Artifact Registry

Cloud Run pulls images from a registry. We will use Google's Artifact Registry (the successor to Container Registry).

1. Create a Repository (One time setup):

Bash

gcloud artifacts repositories create my-repo \
    --repository-format=docker \
    --location=us-central1 \
    --description="Docker repository"
Enter fullscreen mode Exit fullscreen mode

2. Tag your image: Replace PROJECT_ID with your actual Google Cloud Project ID.

Bash

docker tag logicloop-api us-central1-docker.pkg.dev/PROJECT_ID/my-repo/logicloop-api:v1
Enter fullscreen mode Exit fullscreen mode

3. Push the image:

Bash

docker push us-central1-docker.pkg.dev/PROJECT_ID/my-repo/logicloop-api:v1
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy to Cloud Run

Now for the magic command. This tells Google to take that image and turn it into a live HTTPS endpoint.

Bash

gcloud run deploy logicloop-backend \
    --image us-central1-docker.pkg.dev/PROJECT_ID/my-repo/logicloop-api:v1 \
    --platform managed \
    --region us-central1 \
    --allow-unauthenticated
Enter fullscreen mode Exit fullscreen mode
  • --allow-unauthenticated: Makes the API public (great for testing). For internal APIs, you would remove this.

The Result: Google will spit out a URL that looks like: https://logicloop-backend-ue542.a.run.app

Click it, add /docs, and congratulations - your API is live on the internet, secured with HTTPS automatically.


Important Architecture Note: The Database

If you followed my previous guide using SQLite, you need to know a critical Cloud Run limitation: The filesystem is ephemeral.

If Cloud Run scales down or restarts your container (which happens often), your SQLite file is deleted and your data resets.

For Production: You must connect this service to a managed database like Google Cloud SQL (Postgres). You would pass the connection string as an Environment Variable:

Bash

gcloud run deploy logicloop-backend \
    --set-env-vars DATABASE_URL="postgresql+asyncpg://user:pass@IP:5432/dbname" ...
Enter fullscreen mode Exit fullscreen mode

This keeps your application "stateless" (the code) while your data lives safely in a persistent "stateful" layer (the database).


What's Next?

We have Code and Deployment. But running docker build manually every time you change a line of code is painful.

In the next article, we will automate this pipeline using GitHub Actions for a full CI/CD workflow.

Top comments (0)