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