A practical walkthrough for creating and deploying multiple App Engine services with request routing.
Multi-Service Architecture
Each App Engine service is an independent component with its own code, configuration, and runtime. You deploy each service separately, and they communicate via HTTP or dispatch.yaml routing rules.
my-project/
├── default-service/
│ ├── app.yaml # service: default (or omit)
│ ├── main.py
│ └── requirements.txt
├── api-service/
│ ├── app.yaml # service: api
│ ├── main.py
│ └── requirements.txt
├── worker-service/
│ ├── app.yaml # service: worker
│ ├── main.py
│ └── requirements.txt
└── dispatch.yaml # routing rules (project root)Key Insight: Each service lives in its own directory with its own
app.yaml. Theservicefield inapp.yamldefines the service name. The first service deployed becomes thedefaultservice.
Step-by-Step Example
This example creates three services: a web frontend (default), a REST API (api), and a background worker (worker).
1. Default Service (Web Frontend)
default-service/app.yaml:
runtime: python312
# service: default -- optional, first deploy is automatically "default"
instance_class: F2
automatic_scaling:
min_instances: 0
max_instances: 10
target_cpu_utilization: 0.65
handlers:
- url: /static
static_dir: static
- url: /.*
script: auto
secure: alwaysdefault-service/main.py:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Welcome to the web frontend!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)default-service/requirements.txt:
Flask==3.0.0
gunicorn==21.2.02. API Service
api-service/app.yaml:
runtime: python312
service: api
instance_class: F2
automatic_scaling:
min_instances: 1
max_instances: 5
target_cpu_utilization: 0.65api-service/main.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/api/status")
def status():
return jsonify({"status": "healthy", "service": "api"})
@app.route("/api/data")
def data():
return jsonify({"items": ["a", "b", "c"]})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)3. Worker Service
worker-service/app.yaml:
runtime: python312
service: worker
instance_class: B2
basic_scaling:
max_instances: 3
idle_timeout: 10mworker-service/main.py:
from flask import Flask, request
app = Flask(__name__)
@app.route("/tasks/process")
def process():
task_id = request.args.get("id", "unknown")
return f"Processing task {task_id}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)4. Dispatch Rules
dispatch.yaml (at project root):
dispatch:
# Route API requests to the api service
- url: "*/api/*"
service: api
# Route task processing to the worker service
- url: "*/tasks/*"
service: worker
# Everything else goes to the default service
- url: "*/*"
service: defaultRules are evaluated in top-to-bottom order. First match wins.
Deployment
Deploy each service individually, then deploy the dispatch rules:
# Deploy the default service first (required)
cd default-service
gcloud app deploy app.yaml
# Deploy the API service
cd ../api-service
gcloud app deploy app.yaml
# Deploy the worker service
cd ../worker-service
gcloud app deploy app.yaml
# Deploy dispatch rules
cd ..
gcloud app deploy dispatch.yamlNote: The
defaultservice must be deployed first. You cannot deploy other services until thedefaultservice exists.
Accessing Your Services
After deployment, each service is accessible via its own URL:
| Service | URL Pattern | Example |
|---|---|---|
| default | https://PROJECT_ID.REGION_ID.r.appspot.com | https://my-project.uc.r.appspot.com |
| api | https://api-dot-PROJECT_ID.REGION_ID.r.appspot.com | https://api-dot-my-project.uc.r.appspot.com |
| worker | https://worker-dot-PROJECT_ID.REGION_ID.r.appspot.com | https://worker-dot-my-project.uc.r.appspot.com |
With dispatch rules, requests are automatically routed:
https://my-project.uc.r.appspot.com/→defaultservicehttps://my-project.uc.r.appspot.com/api/status→apiservicehttps://my-project.uc.r.appspot.com/tasks/process→workerservice
dispatch.yaml Rules
Syntax
dispatch:
- url: "PATTERN"
service: SERVICE_NAMEURL pattern options
| Pattern | Matches | Example |
|---|---|---|
"*/api/*" | Any host, /api/ path prefix | Routes all API calls |
"example.com/*" | Specific domain only | Custom domain routing |
"*/*" | Everything | Catch-all for default service |
Rules and limits
| Rule | Detail |
|---|---|
| Evaluation order | Top-to-bottom, first match wins |
| Maximum rules | 20 per application |
| Wildcards | * matches any host or path segment |
| Deploy separately | dispatch.yaml is deployed independently from service code |
| Location | Must be in the root directory or alongside the default service |
Warning: If no dispatch rule matches a request, it routes to the
defaultservice. Always include a catch-all rule ("*/*") at the end of your dispatch.yaml.
Key Constraints
| Constraint | Detail |
|---|---|
| Default service required | Every app must have a default service, deployed first |
| Default cannot be deleted | Only non-default services can be removed |
| Service name limits | 1-63 characters, lowercase letters, digits, hyphens |
| Maximum services | 20 per application |
| Independent configuration | Each service has its own runtime, scaling, and instance class |
| Mixed environments | Services can use different environments (Standard/Flexible) |
Common Patterns
| Pattern | Services | Use Case |
|---|---|---|
| Frontend + API | default (Standard) + api (Standard) | Typical web application |
| Web + Worker | default (Standard) + worker (Basic/Manual) | Background task processing |
| Multi-runtime | default (Python) + api (Node.js) | Polyglot microservices |
| Mixed environment | default (Standard) + processing (Flexible) | Standard for web, Flexible for heavy compute |
TL;DR
- Each service gets its own directory with its own
app.yaml. - The
servicefield inapp.yamldefines the service name (omit fordefault). - Deploy the default service first — it’s required before any other service.
- Use dispatch.yaml to route requests between services based on URL patterns.
- Rules are evaluated top-to-bottom, first match wins.
- Services can use different runtimes, environments, and scaling configurations.
- Service limits depend on billing: 5 services for free apps, 210 services for paid apps.
Resources
Creating Multiple Services Official guide to multi-service architecture.
dispatch.yaml Reference Complete reference for dispatch routing rules.
How Requests are Routed Request routing mechanisms and URL formats.
app.yaml Configuration Full reference for the app.yaml configuration file.
Core Components Architecture overview including services and versions.