Oct 27, 20266 min read

How to Deploy Django to Google Cloud Run

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

Django runs well on Cloud Run, but getting there means a container, a Cloud SQL instance, a secret for SECRET_KEY, a service account and the IAM grants that tie them together. Most of that sits outside the Django project, in console clicks or a separate Terraform repository.

This guide deploys a small Django notes API to Cloud Run with Encore, the gcloud CLI and Terraform. Each version uses Cloud SQL Postgres and reads the secret key from Secret Manager.

What you need to decide:

  • How the database and secret are created and kept in sync with the code.
  • Which service account the service runs as, and what it can access.
  • How migrations run before new code takes traffic.
  • How you run the same setup locally.

Django on Cloud Run options at a glance

CapabilityEncoregcloud CLITerraform
Cloud SQLProvisioned from postgres.Databasegcloud sql instances creategoogle_sql_database_instance
SECRET_KEYSharedSecret, set per environmentSecret Manager plus --set-secretsgoogle_secret_manager_secret
IAMService account and grants created on deployGrant each role by handDeclare each binding
Local Postgresencore run starts itRun your ownRun your own
ContainerBuilt on deployYour DockerfileYour Dockerfile and a registry

Encore: deploy Django to Cloud Run from your code

Encore

Encore combines the database and secrets your Django code declares with a local environment and a deploy to Cloud Run in your own GCP project, so Cloud SQL, the secret and the service come from the same code you run locally. It is an infrastructure SDK added to the project, and Django settings, models and migrations stay as they are.

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 and the secret key are declared in notes/resources.py:

from encore import secrets from encore.storage import postgres # Cloud SQL for PostgreSQL on GCP, a local database under encore run. db = postgres.Database("notes") django_secret_key = secrets.SharedSecret[str]("DjangoSecretKey")

settings.py reads the key and builds DATABASES from the declared database. .run.app covers the Cloud Run service URL. Add your custom domain to ALLOWED_HOSTS before going live:

from psycopg.conninfo import conninfo_to_dict from notes.resources import db, django_secret_key SECRET_KEY = django_secret_key.value DEBUG = False ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]", "testserver", ".run.app"] connection = conninfo_to_dict(db.connection_string) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": connection.pop("dbname"), "USER": connection.pop("user", ""), "PASSWORD": connection.pop("password", ""), "HOST": connection.pop("host", ""), "PORT": connection.pop("port", ""), "OPTIONS": connection, } }

The view is an async Django view using the async ORM:

import json from django.http import HttpRequest, JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from .models import Note @csrf_exempt # Stateless JSON API example without cookie auth. @require_http_methods(["GET", "POST"]) async def notes(request: HttpRequest) -> JsonResponse: if request.method == "POST": note = await Note.objects.acreate(title=json.loads(request.body)["title"]) return JsonResponse({"id": note.pk, "title": note.title}, status=201) rows = [n async for n in Note.objects.order_by("id").values("id", "title")[:100]] return JsonResponse({"notes": rows})

main.py serves the ASGI app with Uvicorn on the address Encore provides, locally and on Cloud Run:

import os import uvicorn if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") host, _, port = os.environ.get("ENCORE_LISTEN_ADDR", "127.0.0.1:8000").rpartition(":") uvicorn.run("config.asgi:application", host=host, port=int(port))

Locally, the secret goes in .secrets.user.toml, which stays out of git, and encore run starts Postgres:

[shared] DjangoSecretKey = "local-dev-only-change-me"

Migrations run through encore exec so they reach the same database, and the API answers on the local gateway:

~/notes
$ encore exec -- uv run python manage.py migrate$ encore run$ curl -X POST localhost:4000/notes/ -d '{"title":"first"}'{"id": 1, "title": "first"}

For production, set the secret once per environment with encore secret set --type production DjangoSecretKey, then deploy to a GCP project you connect once. Encore creates the Cloud Run service, a Cloud SQL instance and the secret in Secret Manager in your GCP project, and provisions a service account for the service. Every pull request can get its own preview environment with a separate database.

A database or secret that an agent adds in Python runs locally first and then becomes a Cloud SQL instance or Secret Manager entry on deploy.

Good to know

Run manage.py migrate as a release step rather than at container start. Django's deployment checklist and the OWASP Django cheat sheet cover the settings to review before launch, such as CSRF_TRUSTED_ORIGINS and secure cookies.

Try Encore →

The gcloud CLI

With plain gcloud, you build the image yourself. A non-root Dockerfile, following Docker's build best practices:

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 && chown -R app /app USER app CMD ["sh", "-c", "uvicorn config.asgi:application --host 0.0.0.0 --port ${PORT:-8080}"]

Then you create the database, the secret, the service account and its grants, and deploy with the Cloud SQL socket mounted, as the Cloud SQL for Cloud Run guide describes:

PROJECT=$(gcloud config get-value project) SA=notes-run@$PROJECT.iam.gserviceaccount.com gcloud sql instances create notes-db --database-version=POSTGRES_17 \ --region=europe-west1 --tier=db-g1-small gcloud sql databases create notes --instance=notes-db gcloud sql users create notes --instance=notes-db --password="$DB_PASSWORD" gcloud iam service-accounts create notes-run gcloud projects add-iam-policy-binding "$PROJECT" \ --member="serviceAccount:$SA" --role=roles/cloudsql.client python -c "import secrets; print(secrets.token_urlsafe(50))" | \ gcloud secrets create django-secret-key --data-file=- printf %s "$DB_PASSWORD" | gcloud secrets create notes-db-password --data-file=- for s in django-secret-key notes-db-password; do gcloud secrets add-iam-policy-binding "$s" \ --member="serviceAccount:$SA" --role=roles/secretmanager.secretAccessor done gcloud run jobs deploy notes-migrate --source . --region=europe-west1 \ --service-account="$SA" \ --set-cloudsql-instances="$PROJECT:europe-west1:notes-db" \ --set-secrets=DJANGO_SECRET_KEY=django-secret-key:latest,DB_PASSWORD=notes-db-password:latest \ --set-env-vars=DB_HOST=/cloudsql/$PROJECT:europe-west1:notes-db \ --command=python --args=manage.py,migrate gcloud run jobs execute notes-migrate --region=europe-west1 --wait gcloud run deploy notes --source . --region=europe-west1 \ --service-account="$SA" \ --add-cloudsql-instances="$PROJECT:europe-west1:notes-db" \ --set-secrets=DJANGO_SECRET_KEY=django-secret-key:latest,DB_PASSWORD=notes-db-password:latest \ --set-env-vars=DB_HOST=/cloudsql/$PROJECT:europe-west1:notes-db

Good to know

Settings read DJANGO_SECRET_KEY, DB_PASSWORD and DB_HOST from the environment instead. Cloud Run runs the service as the service identity you pass, which is why the dedicated account and its two roles are created first. None of this exists locally, so you run Postgres yourself for development.

Terraform

Terraform declares the same resources so they can be reviewed and reapplied:

resource "google_service_account" "notes" { account_id = "notes-run" } resource "google_project_iam_member" "sql" { project = var.project role = "roles/cloudsql.client" member = "serviceAccount:${google_service_account.notes.email}" } resource "google_secret_manager_secret_iam_member" "key" { secret_id = google_secret_manager_secret.django_key.id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.notes.email}" }

Good to know

A full setup also declares the Cloud SQL instance, database, user, secrets and versions, the Cloud Run service with its Cloud SQL volume, and an Artifact Registry repository, and it keeps a state backend. The configuration lives apart from the Django code, so a new setting or secret means a change in both places.

How to choose

  • Encore if you want Cloud SQL, the secret and the service account to follow from the Django code, with the same database running locally.
  • gcloud for a one-off service you set up once and rarely change.
  • Terraform if your 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 Django project to Cloud Run.

Frequently asked questions

How do I deploy Django to Google Cloud Run?

Run Django under an ASGI or WSGI server listening on the port Cloud Run provides, connect it to Cloud SQL and read SECRET_KEY from Secret Manager. With Encore, declare postgres.Database("notes") and secrets.SharedSecret[str]("DjangoSecretKey"), read the key with django_secret_key.value in settings.py, and deploy. Encore provisions Cloud Run, Cloud SQL and the secret in your GCP project.

How does Django on Cloud Run connect to Cloud SQL?

Cloud Run can mount the Cloud SQL instance as a Unix socket under /cloudsql/PROJECT:REGION:INSTANCE, and Django uses that path as HOST. The service account running the service needs the Cloud SQL Client role.

Where should Django's SECRET_KEY live on Cloud Run?

In Secret Manager, exposed to the service as an environment variable or file. The service account needs the Secret Manager Secret Accessor role on that secret.

How do I run Django migrations on Cloud Run?

Run them as a separate step before the new revision takes traffic, for example as a Cloud Run job using the same image and running python manage.py migrate. Running them on every container start races when several instances boot at once.

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