CtrlK
BlogDocsLog inGet started
Tessl Logo

google-app-engine

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.

Install with Tessl CLI

npx tessl i github:jezweb/claude-skills --skill google-app-engine
What are skills?

90

Does it follow best practices?

Validation for skill structure

SKILL.md
Review
Evals

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

# 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

# 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

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

AspectStandard
StartupFast (milliseconds)
ScalingScale to zero
PricingPay per request
RuntimesPython 3.8-3.12
Instance ClassesF1, F2, F4, F4_1G
Max Request60 seconds
BackgroundCloud Tasks only
# app.yaml - Standard
runtime: python312
instance_class: F2

Flexible Environment

Use when: Need custom runtimes, Docker, longer request timeouts, or background threads.

AspectFlexible
StartupSlower (minutes)
ScalingMin 1 instance
PricingPer-hour VM
RuntimesAny (Docker)
Max Request60 minutes
BackgroundNative threads
# 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.

# 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',
        }
    }
# 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

# 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)

# app.yaml
handlers:
  - url: /static
    static_dir: staticfiles/
    secure: always
    expiration: "1d"
# 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)

# 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",
        },
    },
}
# 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

# 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

# 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')
# 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:YOUR_PROJECT@appspot.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

Scaling Configuration

Automatic Scaling (Default)

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)

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)

manual_scaling:
  instances: 3

Use for: Predictable workloads, WebSocket connections, or when you need guaranteed capacity.


Instance Classes

ClassMemoryCPUCost/hour
F1256 MB600 MHz$0.05
F2512 MB1.2 GHz$0.10
F41 GB2.4 GHz$0.20
F4_1G2 GB2.4 GHz$0.30
# 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:

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:

python manage.py collectstatic --noinput

And ensure static handler comes before catch-all:

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

# 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
# dispatch.yaml
dispatch:
  - url: "*/api/*"
    service: api
  - url: "*/tasks/*"
    service: worker
# Deploy all services
gcloud app deploy app.yaml api/app.yaml worker/app.yaml dispatch.yaml

Common Patterns

Health Check Endpoint

# urls.py
urlpatterns = [
    path('_ah/health', lambda r: HttpResponse('ok')),
    # ... other urls
]
# 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

# app.yaml
inbound_services:
  - warmup
# 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

# app.yaml
handlers:
  - url: /.*
    script: auto
    secure: always  # Redirects HTTP to HTTPS

Local Development

Using Cloud SQL Proxy

# 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)

# Not recommended - use Django's runserver instead
dev_appserver.py app.yaml

Environment Detection

# 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


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

Repository
jezweb/claude-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.