Oct 27, 20266 min read

How to Deploy Flask to Google Cloud Run

Getting a Flask app onto Cloud Run with Cloud SQL Postgres, a secret key in Secret Manager and a non-root container, using Encore, the gcloud CLI or Terraform.

Flask apps fit Cloud Run well, since a container that answers HTTP on one port is all Cloud Run needs. The surrounding setup takes longer: a Cloud SQL instance, a secret for SECRET_KEY, a service account with the right roles and a way to run the same database locally.

This guide deploys a Flask books API to Cloud Run with Encore, the gcloud CLI and Terraform. Each version uses Cloud SQL Postgres and loads the Flask secret key from Secret Manager.

What you need to decide:

  • How Cloud SQL, the secret and the service account are created.
  • How schema migrations run before new code serves traffic.
  • Which WSGI server runs Flask in the container.
  • How development gets the same Postgres.

Flask on Cloud Run options at a glance

CapabilityEncoregcloud CLITerraform
Cloud SQLFrom postgres.Databasegcloud sql instances creategoogle_sql_database_instance
MigrationsSQL files applied on deployYour own release stepYour own release step
SecretsSharedSecret, set per environment--set-secretsgoogle_secret_manager_secret
IAMCreated on deployGranted by handDeclared per binding
Local Postgresencore run starts itRun your ownRun your own

Encore: deploy Flask to Cloud Run from your code

Encore

Encore combines the database and secrets your Flask code declares with a local environment and a deploy to Cloud Run in your own GCP project, so Cloud SQL and Secret Manager come from the same code that runs on your laptop. It is an infrastructure SDK added to an existing app, and the routes stay ordinary Flask.

Encore declares infrastructure in Python code, runs it locally and provisions it in your AWS or GCP account
Infrastructure declared in Python, running locally under encore run and provisioned in your own AWS or GCP account on deploy.

The database points at a directory of SQL migrations, and the secret key is a shared secret:

from encore import secrets from encore.storage import postgres from flask import Flask # Cloud SQL on GCP, RDS on AWS, and `encore run` starts Postgres locally. db = postgres.Database("books", migrations="./migrations") # Set per environment with `encore secret set`, stored in Secret Manager on GCP. flask_secret = secrets.SharedSecret[str]("FlaskSecretKey") app = Flask(__name__) app.config["SECRET_KEY"] = flask_secret.value

The first migration creates the table:

CREATE TABLE books ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, author TEXT NOT NULL );

Routes query with parameters, and a health route gives Cloud Run something cheap to probe:

from flask import abort, request @app.post("/books") async def create_book(): data = request.get_json() row = await db.query( "INSERT INTO books (title, author) VALUES ($1, $2) RETURNING id", data["title"], data["author"], ).one() return {"id": row["id"]}, 201 @app.get("/books/<int:book_id>") async def get_book(book_id: int): row = await db.query("SELECT id, title, author FROM books WHERE id = $1", book_id).first() if row is None: abort(404) return {"id": row["id"], "title": row["title"], "author": row["author"]} @app.get("/healthz") def healthz(): return {"ok": True}

Waitress serves the app on the address Encore assigns, locally and in the cloud:

import os from waitress import serve if __name__ == "__main__": host, _, port = os.environ.get("ENCORE_LISTEN_ADDR", "0.0.0.0:8000").rpartition(":") serve(app, host=host or "0.0.0.0", port=int(port))

Locally, the key comes from .secrets.user.toml and encore run starts Postgres and applies the migration. For Cloud Run, set the secret once per environment and push:

~/books
$ encore run$ curl -X POST localhost:4000/books -d '{"title":"Dune","author":"Herbert"}' -H 'Content-Type: application/json'{"id": 1}$ encore secret set --type production FlaskSecretKey$ git push encore

On deploy, Encore creates the Cloud Run service, a Cloud SQL instance with the migration applied and the secret in Secret Manager in your GCP project, and provisions a service account for the service. Each environment gets its own database and secret values.

An agent can add a table or a secret, run it locally, and the Cloud Run side follows on deploy from the same commit.

Good to know

Cloud Run scales to zero by default, so the first request after idle includes a cold start, and a minimum instance count avoids it. Async views need flask[async] in the dependencies.

Try Encore →

The gcloud CLI

With gcloud, you write the container. Add a .dockerignore next to the Dockerfile so local secret files stay out of the image:

.secrets.user.toml .env .git

This Dockerfile runs as a non-root user with Gunicorn:

FROM python:3.13-slim ENV PYTHONUNBUFFERED=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN useradd --create-home app USER app CMD exec gunicorn --bind 0.0.0.0:${PORT:-8080} --workers 2 main:app

Then you create every resource and grant before the deploy, following Google's Python quickstart:

PROJECT=$(gcloud config get-value project) SA=books-run@$PROJECT.iam.gserviceaccount.com gcloud sql instances create books-db --database-version=POSTGRES_17 \ --region=europe-west1 --tier=db-g1-small gcloud sql databases create books --instance=books-db gcloud sql users create books --instance=books-db --password="$DB_PASSWORD" gcloud iam service-accounts create books-run gcloud projects add-iam-policy-binding "$PROJECT" \ --member="serviceAccount:$SA" --role=roles/cloudsql.client python -c "import secrets; print(secrets.token_hex(32))" | \ gcloud secrets create flask-secret-key --data-file=- printf %s "$DB_PASSWORD" | gcloud secrets create books-db-password --data-file=- for s in flask-secret-key books-db-password; do gcloud secrets add-iam-policy-binding "$s" \ --member="serviceAccount:$SA" --role=roles/secretmanager.secretAccessor done gcloud run deploy books --source . --region=europe-west1 \ --service-account="$SA" \ --add-cloudsql-instances="$PROJECT:europe-west1:books-db" \ --set-secrets=SECRET_KEY=flask-secret-key:latest,DB_PASSWORD=books-db-password:latest

Good to know

The app reads SECRET_KEY and DB_PASSWORD from the environment and connects through /cloudsql/.... Migrations need their own step, such as a Cloud Run job running Alembic, and local development uses a Postgres you run yourself.

Terraform

Terraform declares the same pieces so changes go through review:

resource "google_cloud_run_v2_service" "books" { name = "books" location = "europe-west1" template { service_account = google_service_account.books.email containers { image = var.image env { name = "SECRET_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.flask_key.secret_id version = "latest" } } } } } }

Good to know

The full configuration also declares the service account, both IAM bindings, the Cloud SQL instance, database and user, the secret and its version, an Artifact Registry repository and the state backend. Building and pushing the image is a separate CI step.

How to choose

  • Encore if you want Cloud SQL, secrets and the service account to follow from the Flask code, with the same Postgres running locally.
  • gcloud for a single service you set up once.
  • Terraform if the team already manages GCP with it and wants every resource written out.

Python support in Encore is in beta, and you can sign up for access to deploy your Flask app to Cloud Run.

Frequently asked questions

How do I deploy a Flask app to Google Cloud Run?

Serve Flask with a production WSGI server on the port Cloud Run provides, connect to Cloud SQL and load secrets from Secret Manager. With Encore, declare postgres.Database("books", migrations="./migrations") and secrets.SharedSecret[str]("FlaskSecretKey"), set the secret with encore secret set and deploy. Encore provisions Cloud Run, Cloud SQL and the secret in your GCP project.

Which server should Flask use on Cloud Run?

A production WSGI server such as Gunicorn or Waitress, never the Flask development server. It must listen on 0.0.0.0 and the port in the PORT environment variable, which defaults to 8080.

How does Flask on Cloud Run connect to Cloud SQL?

Mount the instance with --add-cloudsql-instances, which exposes a Unix socket under /cloudsql/PROJECT:REGION:INSTANCE, and point the connection at that path. The service account needs the Cloud SQL Client role.

Does Cloud Run need a Dockerfile for Flask?

No. gcloud run deploy --source can build a Python app with Google Cloud's buildpacks. A Dockerfile gives you control over the base image and lets you run as a non-root user.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders