Deploy Django on Google Kubernetes Engine (GKE)

Deploy Django on GKE Autopilot: push images to Artifact Registry, connect to Cloud SQL with the Auth Proxy sidecar and Workload Identity, expose HTTPS with a Gateway or Ingress and managed certificates, and run migrations as a Job.

Problem statement

Google Kubernetes Engine runs Kubernetes with Google managing the control plane, and in Autopilot mode the nodes too. For a Django app that has outgrown a single server or Cloud Run's request model (Celery workers, WebSockets, many services), GKE is a natural step on Google Cloud. The Kubernetes basics are the same everywhere; the GKE-specific pieces are Artifact Registry for images, Cloud SQL with the Auth Proxy, Workload Identity for credentials, and Google-managed load balancers and certificates.

Django on GKE AutopilotArtifact Registryversioned imagesDeployment + proxyCloud SQL sidecarWorkload Identityno key filesGateway + certmanaged HTTPSGKE handles nodes and load balancing; you design pods, identity, and database access.
  1. Create a GKE Autopilot cluster and an Artifact Registry repository; push the Django image.
  2. Create a Cloud SQL for PostgreSQL instance and a Kubernetes service account bound to a Google service account via Workload Identity with roles/cloudsql.client.
  3. Run the Cloud SQL Auth Proxy as a native sidecar container; Django connects to 127.0.0.1:5432.
  4. Store SECRET_KEY and database credentials in Secret Manager or Kubernetes Secrets.
  5. Expose the app with a Gateway (or Ingress) and a Google-managed certificate; set readiness probes and a BackendConfig or HealthCheckPolicy path.
  6. Run migrate as a Kubernetes Job before rolling out the Deployment.

Step-by-step solution

1. Cluster and registry

gcloud container clusters create-auto django-prod --region europe-west1
gcloud artifacts repositories create apps --repository-format=docker --location=europe-west1
docker build -t europe-west1-docker.pkg.dev/$PROJECT/apps/myapp:$SHA .
docker push europe-west1-docker.pkg.dev/$PROJECT/apps/myapp:$SHA

Autopilot bills per pod resource request, so set realistic requests on every container.

2. Workload Identity for Cloud SQL and other APIs

gcloud iam service-accounts create django-app
gcloud projects add-iam-policy-binding $PROJECT --member "serviceAccount:django-app@$PROJECT.iam.gserviceaccount.com" --role roles/cloudsql.client
kubectl create serviceaccount django -n app
gcloud iam service-accounts add-iam-policy-binding django-app@$PROJECT.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser --member "serviceAccount:$PROJECT.svc.id.goog[app/django]"
kubectl annotate serviceaccount django -n app iam.gke.io/gcp-service-account=django-app@$PROJECT.iam.gserviceaccount.com

Pods using the django service account now get Google credentials automatically, for Cloud SQL, Cloud Storage (django-storages GCS backend), and Secret Manager, with no JSON key files.

request path and identity on GKEGateway / IngressGoogle load balancer, managed certDjango pods (Gunicorn)readiness probe on /health/Cloud SQL Auth Proxy sidecarIAM auth, TLS to Cloud SQLWorkload Identitypod gets a Google service accountCloud SQL, GCS, Secret Managerno key files anywhereWorkload Identity replaces every downloaded service account keydjango-deployment.com
If you see GOOGLE_APPLICATION_CREDENTIALS pointing at a JSON file, replace it with Workload Identity.

3. Deployment with the Auth Proxy sidecar

apiVersion: apps/v1
kind: Deployment
metadata: { name: web, namespace: app }
spec:
  replicas: 2
  selector: { matchLabels: { app: web } }
  template:
    metadata: { labels: { app: web } }
    spec:
      serviceAccountName: django
      initContainers:
        - name: cloud-sql-proxy
          image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.0
          restartPolicy: Always            # native sidecar
          args: ["--structured-logs", "--port=5432", "PROJECT:REGION:INSTANCE"]
          resources: { requests: { cpu: 100m, memory: 128Mi } }
      containers:
        - name: web
          image: europe-west1-docker.pkg.dev/PROJECT/apps/myapp:SHA
          ports: [{ containerPort: 8000 }]
          envFrom: [{ secretRef: { name: django-env } }]
          env: [{ name: DATABASE_URL, value: "postgres://app:$(DB_PASSWORD)@127.0.0.1:5432/app" }]
          readinessProbe:
            httpGet: { path: /health/, port: 8000, httpHeaders: [{ name: Host, value: example.com }] }
          resources: { requests: { cpu: 500m, memory: 512Mi } }

Running the proxy as a native sidecar (an init container with restartPolicy: Always) means it starts before Django and stops after it, which also makes migration Jobs complete cleanly.

4. HTTPS with a Gateway

Use the Gateway API with a gke-l7-global-external-managed GatewayClass and a Google-managed certificate (Certificate Manager), or the classic Ingress with a ManagedCertificate. Set the load balancer health check to /health/; GKE derives it from the readiness probe or from a HealthCheckPolicy. In Django:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
ALLOWED_HOSTS = ["example.com"]

5. Migrations as a Job

Run a Job with the same image, service account, and sidecar before updating the Deployment:

kubectl -n app create job migrate-$SHA --from=cronjob/migrate-template   # or apply a Job manifest
kubectl -n app wait --for=condition=complete job/migrate-$SHA --timeout=600s
kubectl -n app set image deploy/web web=europe-west1-docker.pkg.dev/$PROJECT/apps/myapp:$SHA

See Run Django Migrations as a Kubernetes Job.

one release on GKEPush imageArtifact Registry, tag SHAMigration Jobsame image + proxyJob completeschema readyRolling updatereadiness gates trafficOld pods drainedgraceful SIGTERMThe Deployment only changes after the migration Job succeedsdjango-deployment.com
Readiness probes and graceful shutdown together make the rollout invisible to users.

Verification check: kubectl -n app rollout status deploy/web succeeds, the Gateway shows a healthy backend, https://example.com serves with a Google-managed certificate, and Cloud SQL's connections view shows connections from the proxy.

Explanation

GKE Autopilot removes node management: you declare pods and their resource requests, and Google provisions capacity. Django needs three integrations to run well there: a secure path to the database (the Cloud SQL Auth Proxy handles IAM authentication and encryption, avoiding public IPs and allow-lists), credentials without key files (Workload Identity maps Kubernetes service accounts to Google service accounts), and HTTPS ingress (Google's load balancers with managed certificates). Everything else, including Celery workers as separate Deployments and Beat as a single replica, follows standard Kubernetes patterns.

Edge cases / notes

  • Private IP. Cloud SQL with private IP plus the proxy's --private-ip flag keeps traffic inside the VPC.
  • Celery runs as its own Deployment with the same sidecar; Beat must have exactly one replica.
  • Cost. Autopilot charges for requested resources; idle replicas still cost money. Cloud Run may be cheaper for low traffic.
  • Static files can be served by WhiteNoise or uploaded to GCS behind Cloud CDN.

Next steps

FAQ

Should I use GKE Autopilot or Standard for Django?

Autopilot for most teams: no node management and per-pod billing. Standard if you need node-level control.

How does Django connect to Cloud SQL on GKE?

Through the Cloud SQL Auth Proxy as a sidecar, authenticated with Workload Identity, connecting to 127.0.0.1.

Do I need service account key files?

No. Workload Identity gives pods Google credentials without keys.

When is Cloud Run better than GKE?

For a single web service with request-driven traffic and no long-running workers.

Keep going