Process Orchestration
Process Orchestration is pyserve's flagship feature for running multiple Python web applications with full process isolation. Each application runs in its own subprocess with independent lifecycle, health monitoring, and automatic restart on failure.
Overview
Unlike ASGI Mounting which runs apps in-process, Process Orchestration provides:
- Process Isolation — Each app runs in a separate Python process
- Health Monitoring — Automatic health checks with configurable intervals
- Auto-restart — Failed processes restart with exponential backoff
- Multi-worker Support — Configure multiple uvicorn workers per app
- Dynamic Port Allocation — Automatic port assignment (9000-9999)
- WSGI Support — Flask/Django apps via automatic wrapping
- Request Tracing — X-Request-ID propagation through proxied requests
Architecture
PyServe Gateway (:8000)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
FastAPI Flask Starlette
:9001 :9002 :9003
/api/* /admin/* /ws/*
PyServe acts as a gateway, routing requests to the appropriate subprocess based on URL path.
Basic Configuration
server:
host: 0.0.0.0
port: 8000
extensions:
- type: process_orchestration
config:
apps:
- name: api
path: /api
app_path: myapp.api:app
- name: admin
path: /admin
app_path: myapp.admin:app
App Configuration Options
- name
- Unique identifier for the application (required)
- path
- URL path prefix for routing requests (required)
- app_path
- Python import path. Format:
module:attribute(required) - app_type
- Application type:
asgiorwsgi. Default:asgi - workers
- Number of uvicorn workers. Default:
1 - port
- Fixed port number. Default: auto-allocated from port_range
- factory
- If
true, app_path points to a factory function. Default:false - env
- Environment variables to pass to the subprocess
- module_path
- Path to add to
sys.pathfor module resolution
Health Check Options
- health_check_enabled
- Enable health monitoring. Default:
true - health_check_path
- Endpoint to check for health. Default:
/health - health_check_interval
- Interval between health checks in seconds. Default:
10.0 - health_check_timeout
- Timeout for health check requests. Default:
5.0 - health_check_retries
- Failed checks before restart. Default:
3
Restart Options
- max_restart_count
- Maximum restart attempts before giving up. Default:
5 - restart_delay
- Initial delay between restarts in seconds. Default:
1.0 - shutdown_timeout
- Timeout for graceful shutdown. Default:
30.0
Global Configuration
extensions:
- type: process_orchestration
config:
port_range: [9000, 9999]
health_check_enabled: true
proxy_timeout: 60.0
logging:
httpx_level: warning
proxy_logs: true
health_check_logs: false
apps:
# ...
- port_range
- Range for dynamic port allocation. Default:
[9000, 9999] - proxy_timeout
- Timeout for proxied requests in seconds. Default:
60.0 - logging.httpx_level
- Log level for HTTP client (debug/info/warning/error). Default:
warning - logging.proxy_logs
- Log proxied requests with latency. Default:
true - logging.health_check_logs
- Log health check results. Default:
false
FastAPI Example
# myapp/api.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users")
async def get_users():
return [{"id": 1, "name": "Alice"}]
extensions:
- type: process_orchestration
config:
apps:
- name: api
path: /api
app_path: myapp.api:app
workers: 4
health_check_path: /health
Requests to /api/users are proxied to the FastAPI process as /users.
Flask Example (WSGI)
# myapp/admin.py
from flask import Flask
app = Flask(__name__)
@app.route("/health")
def health():
return {"status": "ok"}
@app.route("/dashboard")
def dashboard():
return {"page": "dashboard"}
extensions:
- type: process_orchestration
config:
apps:
- name: admin
path: /admin
app_path: myapp.admin:app
app_type: wsgi
workers: 2
Note: WSGI support requires
a2wsgi package:
pip install a2wsgi
Factory Pattern
# myapp/api.py
from fastapi import FastAPI
def create_app(debug: bool = False) -> FastAPI:
app = FastAPI(debug=debug)
@app.get("/health")
async def health():
return {"status": "ok", "debug": debug}
return app
apps:
- name: api
path: /api
app_path: myapp.api:create_app
factory: true
Environment Variables
Pass environment variables to subprocesses:
apps:
- name: api
path: /api
app_path: myapp.api:app
env:
DATABASE_URL: "postgresql://localhost/mydb"
REDIS_URL: "redis://localhost:6379"
DEBUG: "false"
Multiple Applications
extensions:
- type: process_orchestration
config:
port_range: [9000, 9999]
apps:
# FastAPI REST API
- name: api
path: /api
app_path: apps.api:app
workers: 4
# Flask Admin Panel
- name: admin
path: /admin
app_path: apps.admin:app
app_type: wsgi
workers: 2
# Starlette WebSocket Handler
- name: websocket
path: /ws
app_path: apps.websocket:app
workers: 1
Request Tracing
PyServe automatically generates and propagates X-Request-ID headers:
- If a request has
X-Request-ID, it's preserved - Otherwise, a UUID is generated
- The ID is passed to subprocesses and included in response headers
- All logs include the request ID for tracing
Process Orchestration vs ASGI Mount
| Feature | Process Orchestration | ASGI Mount |
|---|---|---|
| Isolation | Full process isolation | Shared process |
| Memory | Separate per app | Shared |
| Crash Impact | Only that app restarts | All apps affected |
| Health Checks | Yes, with auto-restart | No |
| Multi-worker | Yes, per app | No |
| Latency | HTTP proxy overhead | In-process (faster) |
| Use Case | Production, isolation needed | Development, simple setups |
When to use Process Orchestration:
- Running multiple apps that shouldn't affect each other
- Need automatic restart on failure
- Different resource requirements per app
- Production deployments
- Development and testing
- Simple setups with trusted apps
- Minimal latency requirements
See Also:
- ASGI Mounting Guide — In-process app mounting
- Extensions Reference — All extension types
- Configuration Guide — Full configuration reference