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:
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.

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:
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.
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.
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
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 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}"
}
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.
Python support in Encore is in beta, and you can sign up for access to deploy your Django project to 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.
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.
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.
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.