home / skills / jezweb / claude-skills / google-app-engine
/skills/google-app-engine
This skill guides deploying Python apps to Google App Engine, configuring app.yaml, Cloud SQL, static assets, and scaling while avoiding common startup and
npx playbooks add skill jezweb/claude-skills --skill google-app-engineReview the files below or copy the command above to add this skill to your agents.
---
name: google-app-engine
description: |
Deploy Python applications to Google App Engine Standard/Flexible. Covers app.yaml configuration, Cloud SQL socket connections, Cloud Storage for static files, scaling settings, and environment variables.
Use when: deploying to App Engine, configuring app.yaml, connecting Cloud SQL, setting up static file serving, or troubleshooting 502 errors, cold starts, or memory limits.
---
# Google App Engine
**Status**: Production Ready
**Last Updated**: 2026-01-24
**Dependencies**: Google Cloud SDK (gcloud CLI)
**Skill Version**: 1.0.0
---
## Quick Start (10 Minutes)
### 1. Prerequisites
```bash
# Install Google Cloud SDK
# macOS
brew install google-cloud-sdk
# Or download from https://cloud.google.com/sdk/docs/install
# Authenticate
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
# Enable required APIs
gcloud services enable appengine.googleapis.com
gcloud services enable sqladmin.googleapis.com
gcloud services enable secretmanager.googleapis.com
```
### 2. Create app.yaml
```yaml
# app.yaml - Standard Environment (Python 3.12)
runtime: python312
instance_class: F2
env_variables:
DJANGO_SETTINGS_MODULE: "myproject.settings.production"
handlers:
# Static files (served directly by App Engine)
- url: /static
static_dir: staticfiles/
secure: always
# All other requests go to the app
- url: /.*
script: auto
secure: always
automatic_scaling:
min_instances: 0
max_instances: 10
target_cpu_utilization: 0.65
```
### 3. Deploy
```bash
# Deploy to App Engine
gcloud app deploy
# Deploy with specific version
gcloud app deploy --version=v1 --no-promote
# View logs
gcloud app logs tail -s default
```
---
## Standard vs Flexible Environment
### Standard Environment (Recommended for Most Apps)
**Use when**: Building typical web apps, APIs, or services that fit within runtime constraints.
| Aspect | Standard |
|--------|----------|
| **Startup** | Fast (milliseconds) |
| **Scaling** | Scale to zero |
| **Pricing** | Pay per request |
| **Runtimes** | Python 3.8-3.12 |
| **Instance Classes** | F1, F2, F4, F4_1G |
| **Max Request** | 60 seconds |
| **Background** | Cloud Tasks only |
```yaml
# app.yaml - Standard
runtime: python312
instance_class: F2
```
### Flexible Environment
**Use when**: Need custom runtimes, Docker, longer request timeouts, or background threads.
| Aspect | Flexible |
|--------|----------|
| **Startup** | Slower (minutes) |
| **Scaling** | Min 1 instance |
| **Pricing** | Per-hour VM |
| **Runtimes** | Any (Docker) |
| **Max Request** | 60 minutes |
| **Background** | Native threads |
```yaml
# app.yaml - Flexible
runtime: python
env: flex
runtime_config:
runtime_version: "3.12"
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
automatic_scaling:
min_num_instances: 1
max_num_instances: 5
```
**Cost Warning**: Flexible always runs at least 1 instance (~$30-40/month minimum).
---
## Cloud SQL Connection
### Standard Environment (Unix Socket)
App Engine Standard connects to Cloud SQL via Unix sockets, not TCP/IP.
```python
# settings.py
import os
if os.getenv('GAE_APPLICATION'):
# Production: Cloud SQL 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: Cloud SQL Proxy or local DB
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'localdb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
```
```yaml
# app.yaml
env_variables:
DB_NAME: "mydb"
DB_USER: "myuser"
CLOUD_SQL_CONNECTION_NAME: "project:region:instance"
# CRITICAL: Beta settings for Cloud SQL socket
beta_settings:
cloud_sql_instances: "project:region:instance"
```
**CRITICAL**: The `beta_settings.cloud_sql_instances` enables the Unix socket. Without it, connection fails.
### Local Development with Cloud SQL Proxy
```bash
# Download and run Cloud SQL Proxy
cloud-sql-proxy PROJECT:REGION:INSTANCE --port=5432
# Or use Docker
docker run -p 5432:5432 \
gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8.0 \
PROJECT:REGION:INSTANCE
```
---
## Static Files with Cloud Storage
### Option 1: App Engine Static Handlers (Simple)
```yaml
# app.yaml
handlers:
- url: /static
static_dir: staticfiles/
secure: always
expiration: "1d"
```
```bash
# Collect static files before deploy
python manage.py collectstatic --noinput
gcloud app deploy
```
**Limitation**: Files bundled with deploy, limited to 32MB per file.
### Option 2: Cloud Storage (Recommended for Production)
```python
# settings.py
from google.cloud import storage
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
STATICFILES_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
DEFAULT_FILE_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
# Or with django-storages
STORAGES = {
"default": {
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
"OPTIONS": {
"bucket_name": GS_BUCKET_NAME,
"location": "media",
},
},
"staticfiles": {
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
"OPTIONS": {
"bucket_name": GS_BUCKET_NAME,
"location": "static",
},
},
}
```
```bash
# Install django-storages
pip install django-storages[google]
# Create bucket with public access for static files
gsutil mb -l us-central1 gs://YOUR_BUCKET_NAME
gsutil iam ch allUsers:objectViewer gs://YOUR_BUCKET_NAME
```
---
## Environment Variables and Secrets
### Simple: app.yaml env_variables
```yaml
# app.yaml - NOT for secrets!
env_variables:
DJANGO_SETTINGS_MODULE: "myproject.settings.production"
DEBUG: "False"
```
**Warning**: `app.yaml` is committed to source control. Never put secrets here.
### Production: Secret Manager
```python
# settings.py
from google.cloud import secretmanager
def get_secret(secret_id, version="latest"):
"""Fetch 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}"
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode("UTF-8")
# Usage
if os.getenv('GAE_APPLICATION'):
SECRET_KEY = get_secret('django-secret-key')
DB_PASSWORD = get_secret('db-password')
```
```bash
# Create secrets
echo -n "your-secret-key" | gcloud secrets create django-secret-key --data-file=-
echo -n "db-password" | gcloud secrets create db-password --data-file=-
# Grant App Engine access
gcloud secrets add-iam-policy-binding django-secret-key \
--member="serviceAccount:[email protected]" \
--role="roles/secretmanager.secretAccessor"
```
---
## Scaling Configuration
### Automatic Scaling (Default)
```yaml
automatic_scaling:
min_instances: 0 # Scale to zero when idle
max_instances: 10 # Cap maximum instances
target_cpu_utilization: 0.65
target_throughput_utilization: 0.6
max_concurrent_requests: 80
min_pending_latency: 30ms
max_pending_latency: automatic
```
### Basic Scaling (Background Tasks)
```yaml
basic_scaling:
max_instances: 5
idle_timeout: 5m
```
**Use for**: Services that need to run background work without scaling to zero immediately.
### Manual Scaling (Fixed Instances)
```yaml
manual_scaling:
instances: 3
```
**Use for**: Predictable workloads, WebSocket connections, or when you need guaranteed capacity.
---
## Instance Classes
| Class | Memory | CPU | Cost/hour |
|-------|--------|-----|-----------|
| F1 | 256 MB | 600 MHz | $0.05 |
| F2 | 512 MB | 1.2 GHz | $0.10 |
| F4 | 1 GB | 2.4 GHz | $0.20 |
| F4_1G | 2 GB | 2.4 GHz | $0.30 |
```yaml
# Recommended for Django
instance_class: F2 # 512MB usually sufficient
```
**Upgrade to F4** if you see memory errors or slow response times.
---
## Known Issues Prevention
This skill prevents **6 documented issues**:
### Issue #1: Cloud SQL Connection Refused
**Error**: `could not connect to server: Connection refused`
**Why It Happens**: Missing `beta_settings.cloud_sql_instances` in app.yaml
**Prevention**: Always include:
```yaml
beta_settings:
cloud_sql_instances: "project:region:instance"
```
### Issue #2: Static Files 404
**Error**: Static files return 404 after deploy
**Why It Happens**: `collectstatic` not run, or handler order wrong
**Prevention**:
```bash
python manage.py collectstatic --noinput
```
And ensure static handler comes before catch-all:
```yaml
handlers:
- url: /static
static_dir: staticfiles/
- url: /.*
script: auto
```
### Issue #3: 502 Bad Gateway
**Error**: 502 errors on first request or under load
**Why It Happens**: Cold start timeout, app takes too long to initialize
**Prevention**:
- Optimize app startup (lazy imports, connection pooling)
- Use `min_instances: 1` to avoid cold starts
- Increase `instance_class` for more CPU/memory
### Issue #4: Memory Limit Exceeded
**Error**: `Exceeded soft memory limit` in logs
**Why It Happens**: F1 class (256MB) too small for Django + dependencies
**Prevention**: Use `instance_class: F2` minimum for Django apps
### Issue #5: Request Timeout
**Error**: `DeadlineExceededError` after 60 seconds
**Why It Happens**: Standard environment has 60s request limit
**Prevention**:
- Move long tasks to Cloud Tasks
- Use Flexible environment for longer timeouts
- Optimize database queries
### Issue #6: Secret Key in Source Control
**Error**: Django `SECRET_KEY` exposed in git history
**Why It Happens**: Putting secrets in `app.yaml` env_variables
**Prevention**: Use Secret Manager (see Environment Variables section)
---
## Deployment Commands
```bash
# Deploy default service
gcloud app deploy
# Deploy specific service
gcloud app deploy app.yaml --service=api
# Deploy without promoting (for testing)
gcloud app deploy --version=v2 --no-promote
# Split traffic between versions
gcloud app services set-traffic default --splits=v1=0.5,v2=0.5
# Promote version
gcloud app versions migrate v2
# View logs
gcloud app logs tail -s default
# Open app in browser
gcloud app browse
# List versions
gcloud app versions list
# Delete old versions
gcloud app versions delete v1 v2 --quiet
```
---
## Multi-Service Architecture
```
myproject/
├── app.yaml # Default service
├── api/
│ └── app.yaml # API service
├── worker/
│ └── app.yaml # Background worker
└── dispatch.yaml # URL routing
```
```yaml
# dispatch.yaml
dispatch:
- url: "*/api/*"
service: api
- url: "*/tasks/*"
service: worker
```
```bash
# Deploy all services
gcloud app deploy app.yaml api/app.yaml worker/app.yaml dispatch.yaml
```
---
## Common Patterns
### Health Check Endpoint
```python
# urls.py
urlpatterns = [
path('_ah/health', lambda r: HttpResponse('ok')),
# ... other urls
]
```
```yaml
# app.yaml
liveness_check:
path: "/_ah/health"
check_interval_sec: 30
timeout_sec: 4
failure_threshold: 2
success_threshold: 2
readiness_check:
path: "/_ah/health"
check_interval_sec: 5
timeout_sec: 4
failure_threshold: 2
success_threshold: 2
```
### Warmup Requests
```yaml
# app.yaml
inbound_services:
- warmup
```
```python
# urls.py
urlpatterns = [
path('_ah/warmup', warmup_view),
]
# views.py
def warmup_view(request):
"""Pre-warm caches and connections."""
from django.db import connection
connection.ensure_connection()
return HttpResponse('ok')
```
### HTTPS Redirect
```yaml
# app.yaml
handlers:
- url: /.*
script: auto
secure: always # Redirects HTTP to HTTPS
```
---
## Local Development
### Using Cloud SQL Proxy
```bash
# Terminal 1: Run Cloud SQL Proxy
cloud-sql-proxy PROJECT:REGION:INSTANCE --port=5432
# Terminal 2: Run Django
export DB_HOST=127.0.0.1
export DB_PORT=5432
python manage.py runserver
```
### Using dev_appserver (Legacy)
```bash
# Not recommended - use Django's runserver instead
dev_appserver.py app.yaml
```
### Environment Detection
```python
# settings.py
import os
# Detect App Engine environment
IS_GAE = os.getenv('GAE_APPLICATION') is not None
IS_GAE_LOCAL = os.getenv('GAE_ENV') == 'localdev'
if IS_GAE:
DEBUG = False
ALLOWED_HOSTS = ['.appspot.com', '.your-domain.com']
else:
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
```
---
## Bundled Resources
### Templates (templates/)
- `app.yaml` - Standard environment configuration
- `app-flex.yaml` - Flexible environment configuration
- `requirements.txt` - Common dependencies for App Engine
### References (references/)
- `instance-classes.md` - Detailed instance class comparison
- `common-errors.md` - Error messages and solutions
---
## Official Documentation
- **App Engine Python**: https://cloud.google.com/appengine/docs/standard/python3
- **app.yaml Reference**: https://cloud.google.com/appengine/docs/standard/reference/app-yaml
- **Cloud SQL**: https://cloud.google.com/sql/docs/postgres/connect-app-engine-standard
- **Secret Manager**: https://cloud.google.com/secret-manager/docs
- **Scaling**: https://cloud.google.com/appengine/docs/standard/how-instances-are-managed
---
## Dependencies
```
# requirements.txt
gunicorn>=21.0.0
google-cloud-secret-manager>=2.16.0
google-cloud-storage>=2.10.0
django-storages[google]>=1.14.0
psycopg2-binary>=2.9.9 # For PostgreSQL
```
---
## Production Checklist
- [ ] `SECRET_KEY` in Secret Manager (not app.yaml)
- [ ] `DEBUG = False` in production settings
- [ ] `ALLOWED_HOSTS` configured for your domain
- [ ] `collectstatic` runs before deploy
- [ ] `beta_settings.cloud_sql_instances` set
- [ ] Instance class appropriate (F2+ for Django)
- [ ] HTTPS enforced (`secure: always`)
- [ ] Health check endpoint configured
- [ ] Error monitoring set up (Cloud Error Reporting)
---
**Last verified**: 2026-01-24 | **Skill version**: 1.0.0
This skill deploys Python web apps to Google App Engine (Standard and Flexible) and provides actionable configuration patterns for app.yaml, Cloud SQL connections, static file handling, scaling, and secrets. It targets production-ready Django and similar frameworks and bundles commands, templates, and troubleshooting guidance for common App Engine issues.
I inspect and generate recommended app.yaml snippets for Standard and Flexible environments, validate settings for scaling, instance classes, and static handlers, and provide code patterns for Cloud SQL Unix socket connections and Cloud Storage-backed static/media storage. I also show secure secret retrieval via Secret Manager and include deployment and debugging commands to diagnose 502s, cold starts, memory limits, and request timeouts.
Why am I getting connection refused to Cloud SQL?
In Standard you must enable the Cloud SQL Unix socket by setting beta_settings.cloud_sql_instances in app.yaml and provide CLOUD_SQL_CONNECTION_NAME via env_variables.
Should I store secrets in app.yaml env_variables?
No — app.yaml is committed to source control. Use Secret Manager and grant the App Engine service account secretAccessor role.
How do I avoid cold start 502s?
Optimize startup (lazy imports, pooled connections), add warmup handlers, or set automatic_scaling.min_instances: 1 to keep instances warm.