Loading...
Loading...
Deploy Django on Google App Engine Standard with Cloud SQL PostgreSQL. Covers Unix socket connections, Cloud SQL Auth Proxy for local dev, Gunicorn configuration, and production-ready settings. Use when: deploying Django to App Engine, configuring Cloud SQL PostgreSQL, setting up Unix socket connections, or troubleshooting "No such file or directory", "connection refused", or "FATAL: password authentication failed".
npx skill4agent add jezweb/claude-skills django-cloud-sql-postgresDjango@5.1psycopg2-binary@2.9.9gunicorn@23.0.0google-cloud-sql-connector@1.12.0pip install Django psycopg2-binary gunicornpip install "cloud-sql-python-connector[pg8000]"psycopg2-binarygunicornimport os
# Detect App Engine environment
IS_APP_ENGINE = os.getenv('GAE_APPLICATION', None)
if IS_APP_ENGINE:
# Production: Connect via Unix socket
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': f"/cloudsql/{os.environ['CLOUD_SQL_CONNECTION_NAME']}",
'PORT': '', # Empty for Unix socket
}
}
else:
# Local development: Connect via Cloud SQL Proxy
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': '127.0.0.1',
'PORT': '5432',
}
}/cloudsql/PROJECT:REGION:INSTANCE127.0.0.1:5432runtime: python310
entrypoint: gunicorn -b :$PORT myproject.wsgi:application
env_variables:
DB_NAME: "mydb"
DB_USER: "postgres"
CLOUD_SQL_CONNECTION_NAME: "project-id:region:instance-name"
# Cloud SQL connection
beta_settings:
cloud_sql_instances: "project-id:region:instance-name"
handlers:
- url: /static
static_dir: static/
- url: /.*
script: auto
secure: alwaysbeta_settings.cloud_sql_instances/cloudsql/...gcloud app deploy# Create PostgreSQL instance
gcloud sql instances create myinstance \
--database-version=POSTGRES_15 \
--tier=db-f1-micro \
--region=us-central1
# Create database
gcloud sql databases create mydb --instance=myinstance
# Create user
gcloud sql users create postgres \
--instance=myinstance \
--password=YOUR_SECURE_PASSWORDPOSTGRES_15db-f1-microdb-g1-smallPROJECT_ID:REGION:INSTANCE_NAMEDjango>=5.1,<6.0
psycopg2-binary>=2.9.9
gunicorn>=23.0.0
whitenoise>=6.7.0import os
# Security settings for production
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = [
'.appspot.com',
'.run.app',
'localhost',
'127.0.0.1',
]
# Static files with WhiteNoise
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Database connection pooling
DATABASES['default']['CONN_MAX_AGE'] = 60 # Keep connections open for 60 secondsCONN_MAX_AGE=60ALLOWED_HOSTS.appspot.com# macOS
brew install cloud-sql-proxy
# Linux
curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.1/cloud-sql-proxy.linux.amd64
chmod +x cloud-sql-proxy# Authenticate first
gcloud auth application-default login
# Start proxy (runs on 127.0.0.1:5432)
./cloud-sql-proxy PROJECT_ID:REGION:INSTANCE_NAME
# Or with specific port
./cloud-sql-proxy PROJECT_ID:REGION:INSTANCE_NAME --port=5432export DB_NAME=mydb
export DB_USER=postgres
export DB_PASSWORD=your_password
export DEBUG=True# Local (with proxy running)
python manage.py migrate
# Verify connection
python manage.py dbshell# Option 1: Run locally with proxy
./cloud-sql-proxy PROJECT:REGION:INSTANCE &
python manage.py migrate
# Option 2: Use Cloud Build (recommended)
# See references/cloud-build-migrations.mdimport multiprocessing
# Workers
workers = 2 # App Engine Standard limits this
threads = 4
worker_class = 'gthread'
# Timeout (App Engine has 60s limit for standard, 3600s for flexible)
timeout = 55
# Logging
accesslog = '-'
errorlog = '-'
loglevel = 'info'
# Bind (App Engine sets $PORT)
bind = f"0.0.0.0:{os.environ.get('PORT', '8080')}"# Simple (recommended for most cases)
entrypoint: gunicorn -b :$PORT myproject.wsgi:application
# With config file
entrypoint: gunicorn -c gunicorn.conf.py myproject.wsgi:application
# With workers and timeout
entrypoint: gunicorn -b :$PORT -w 2 -t 55 myproject.wsgi:applicationgthread# Collect static files
python manage.py collectstatic --noinput
# Deploy
gcloud app deploy app.yaml
# Set DB password via environment
gcloud app deploy --set-env-vars="DB_PASSWORD=your_secure_password"
# View logs
gcloud app logs tail -s default# Open in browser
gcloud app browse
# Check database connection
gcloud app logs read --service=default | grep -i database/cloudsql/PROJECT:REGION:INSTANCEPORT=''CONN_MAX_AGEapp.yamlHOST='localhost'beta_settings.cloud_sql_instancesCONN_MAX_AGE=Nonedjango.db.utils.OperationalError: could not connect to server: No such file or directorybeta_settings.cloud_sql_instancesbeta_settings.cloud_sql_instances: "PROJECT:REGION:INSTANCE"django.db.utils.OperationalError: connection refusedcould not connect to server: Connection refusedcloud-sql-proxy PROJECT:REGION:INSTANCEFATAL: password authentication failed for user "postgres"gcloud sql users list --instance=INSTANCEgcloud sql users set-password postgres --instance=INSTANCE --password=NEW_PASSWORDFATAL: too many connections for role "postgres"remaining connection slots are reservedCONN_MAX_AGE = 60django-db-connection-poolDeadlineExceededErrorstatic/collectstaticpython manage.py collectstatic --noinputForbidden (403) CSRF verification failedCSRF_TRUSTED_ORIGINSCSRF_TRUSTED_ORIGINS = ['https://*.appspot.com']django.db.utils.OperationalError: FATAL: database "mydb" does not existDB_NAMEgcloud sql databases list --instance=INSTANCEDB_NAMEFATAL: Cloud SQL IAM user authentication failedroles/cloudsql.instanceUsergcloud projects add-iam-policy-binding PROJECT --member="serviceAccount:PROJECT@appspot.gserviceaccount.com" --role="roles/cloudsql.instanceUser"SECRET_KEY = os.environ.get('SECRET_KEY')gcloud app deploy --set-env-varsapp_engine_apis: true/_ah/warmupimport os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Security
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = [
'.appspot.com',
'.run.app',
'localhost',
'127.0.0.1',
]
# CSRF for App Engine
CSRF_TRUSTED_ORIGINS = [
'https://*.appspot.com',
'https://*.run.app',
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Your apps here
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Static files
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
# Database
IS_APP_ENGINE = os.getenv('GAE_APPLICATION', None)
if IS_APP_ENGINE:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': f"/cloudsql/{os.environ['CLOUD_SQL_CONNECTION_NAME']}",
'PORT': '',
'CONN_MAX_AGE': 60,
'OPTIONS': {
'connect_timeout': 10,
},
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
'PORT': os.environ.get('DB_PORT', '5432'),
'CONN_MAX_AGE': 60,
}
}
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
'propagate': False,
},
},
}runtime: python310
entrypoint: gunicorn -b :$PORT -w 2 -t 55 --threads 4 myproject.wsgi:application
instance_class: F2
env_variables:
DB_NAME: "mydb"
DB_USER: "postgres"
CLOUD_SQL_CONNECTION_NAME: "project-id:us-central1:instance-name"
DEBUG: "False"
beta_settings:
cloud_sql_instances: "project-id:us-central1:instance-name"
handlers:
- url: /static
static_dir: static/
secure: always
- url: /.*
script: auto
secure: always
automatic_scaling:
min_instances: 0
max_instances: 2
target_cpu_utilization: 0.65F2min_instances: 0target_cpu_utilization: 0.65Django>=5.1,<6.0
psycopg2-binary>=2.9.9
gunicorn>=23.0.0
whitenoise>=6.7.0import os
def get_database_config():
"""Return database config based on environment."""
is_production = os.getenv('GAE_APPLICATION', None)
if is_production:
return {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': f"/cloudsql/{os.environ['CLOUD_SQL_CONNECTION_NAME']}",
'PORT': '',
'CONN_MAX_AGE': 60,
}
else:
return {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': '127.0.0.1',
'PORT': '5432',
'CONN_MAX_AGE': 60,
}
DATABASES = {'default': get_database_config()}from google.cloud import secretmanager
def get_secret(secret_id, version_id='latest'):
"""Retrieve secret from Google Secret Manager."""
client = secretmanager.SecretManagerServiceClient()
project_id = os.environ.get('GOOGLE_CLOUD_PROJECT')
name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode('UTF-8')
# Usage in settings.py
if os.getenv('GAE_APPLICATION'):
SECRET_KEY = get_secret('django-secret-key')
DB_PASSWORD = get_secret('db-password')# For local development without Cloud SQL Auth Proxy
from google.cloud.sql.connector import Connector
def get_db_connection():
connector = Connector()
return connector.connect(
"project:region:instance",
"pg8000",
user="postgres",
password=os.environ["DB_PASSWORD"],
db="mydb",
)
# In settings.py for local dev (requires pg8000 driver)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': 'postgres',
'OPTIONS': {
'get_conn': get_db_connection,
},
}
}# views.py
from django.http import JsonResponse
from django.db import connection
def health_check(request):
"""Health check endpoint for App Engine."""
try:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
return JsonResponse({
'status': 'healthy',
'database': 'connected',
})
except Exception as e:
return JsonResponse({
'status': 'unhealthy',
'database': str(e),
}, status=503)
# urls.py
urlpatterns = [
path('_ah/health', health_check, name='health_check'),
]templates/settings_snippet.pytemplates/app.yamltemplates/requirements.txtreferences/cloud-sql-proxy-setup.mdreferences/iam-authentication.mdreferences/secret-manager.mdreferences/migrations-in-production.mdcloud-sql-proxy-setup.mdiam-authentication.mdmigrations-in-production.md# Enable IAM authentication on instance
gcloud sql instances patch INSTANCE --database-flags cloudsql.iam_authentication=on
# Create IAM user
gcloud sql users create SERVICE_ACCOUNT@PROJECT.iam \
--instance=INSTANCE \
--type=CLOUD_IAM_SERVICE_ACCOUNT
# Grant connect permission
gcloud projects add-iam-policy-binding PROJECT \
--member="serviceAccount:PROJECT@appspot.gserviceaccount.com" \
--role="roles/cloudsql.instanceUser"DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': f"{os.environ['SERVICE_ACCOUNT']}@{os.environ['PROJECT_ID']}.iam",
'HOST': f"/cloudsql/{os.environ['CLOUD_SQL_CONNECTION_NAME']}",
'PORT': '',
# No PASSWORD needed with IAM auth
}
}# Cloud Run service for PgBouncer
# See references/pgbouncer-setup.md for full configurationCONN_MAX_AGE# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/gcloud'
args: ['sql', 'connect', 'INSTANCE', '--quiet']
- name: 'python:3.10'
entrypoint: 'bash'
args:
- '-c'
- |
pip install -r requirements.txt
python manage.py migrate --noinput
env:
- 'DB_NAME=mydb'
- 'DB_USER=postgres'
- 'DB_HOST=/cloudsql/PROJECT:REGION:INSTANCE'
secretEnv: ['DB_PASSWORD']
availableSecrets:
secretManager:
- versionName: projects/PROJECT/secrets/db-password/versions/latest
env: 'DB_PASSWORD'./cloud-sql-proxy PROJECT:REGION:INSTANCE &
python manage.py migrateDjango>=5.1psycopg2-binary>=2.9.9gunicorn>=23.0.0whitenoise>=6.7.0python-dotenv>=1.0.0google-cloud-secret-manager>=2.20.0cloud-sql-python-connector[pg8000]>=1.12.0django-db-connection-pool>=1.2.5{
"dependencies": {
"Django": ">=5.1,<6.0",
"psycopg2-binary": ">=2.9.9",
"gunicorn": ">=23.0.0",
"whitenoise": ">=6.7.0"
},
"optional": {
"google-cloud-secret-manager": ">=2.20.0",
"cloud-sql-python-connector": ">=1.12.0"
}
}No such file or directorybeta_settings.cloud_sql_instancesPROJECT:REGION:INSTANCEgcloud sql instances listHOST127.0.0.1Cloud SQL Clientpython manage.py collectstatic --noinputstatic/beta_settings.cloud_sql_instancesCONN_MAX_AGEgcloud app deploygcloud sql instances list