Deployment
PyWire applications can be deployed anywhere that supports Python and ASGI (e.g., Render, Fly.io, Railway, DigitalOcean, or your own VPS).
pywire deploy
Section titled “pywire deploy”The fastest way to get deployment configs is the deploy command. It builds your project and generates platform-specific configuration files.
pywire deploy --platform dockerPlatforms
Section titled “Platforms”Docker (default) — generates a Dockerfile:
pywire deploy --platform dockerThe generated Dockerfile uses python:3.12-slim, installs dependencies with uv, and runs the app with pywire run.
Render — generates a render.yaml:
pywire deploy --platform renderAfter generating, push to your Git repo and connect it to Render. The render.yaml configures a web service with the correct build and start commands.
Fly.io — generates a fly.toml and a Dockerfile:
pywire deploy --platform flyThen deploy with the Fly CLI:
# One-time setupfly launch --no-deploy # imports fly.toml
# Deployfly deployTo scale to multiple machines (fly scale count N), you need sticky sessions or a shared Redis instance — see Horizontal Scaling.
Railway — generates a railway.json and Dockerfile:
pywire deploy --platform railwayRailway auto-detects the Dockerfile. Deploy with the Railway CLI:
railway login && railway initrailway upCloudflare Workers — generates wrangler.toml, entry.py, and pywire_do.py:
pywire deploy --platform cloudflareCloudflare Workers use a fundamentally different architecture from container-based platforms. Instead of a long-running server, each user session runs in a Durable Object with persistent storage and WebSocket hibernation support. Static assets are served from Cloudflare’s edge CDN.
Local development:
# Standard PyWire dev server (fast hot-reload, recommended for daily dev)pywire dev
# Cloudflare Workers local runtime (tests workerd compatibility)pywire build --platform cloudflareuv run pywrangler devDeploy:
pywire build --platform cloudflareuv run pywrangler deployArchitecture notes:
- Each session gets its own Durable Object instance with persistent storage
- Real-time reactivity works out of the box via WebSocket hibernation
- No Redis needed — Durable Objects handle session state
--workersand--redisflags are not applicable (Durable Objects replace both)
Current limitations:
- Cold starts (2-4 seconds): Cloudflare Python Workers + Durable Objects have inherent cold start latency due to Pyodide (WebAssembly Python) snapshot restoration and DO initialization. PyWire mitigates this by pre-warming the Durable Object during the initial HTTP request — the DO starts initializing while the browser loads HTML and JavaScript. Pages are server-rendered, so content is visible immediately; the cold start only affects interactivity.
- No pydantic support: The
pywire[forms]extra (pydantic form validation) is excluded from Cloudflare deployments becausepydantic_core(4.3 MiB WASM binary) would exceed the bundle size limit. Standard HTML form validation still works. - Platform maturity: Cloudflare Python Workers launched in late 2024 and is actively being improved. Cold start performance is expected to improve as Cloudflare optimizes Pyodide snapshot restoration and Durable Object initialization. Follow Cloudflare’s Python Workers changelog for updates.
For latency-sensitive applications, container-based deployments (Docker, Railway, Render, Fly.io) provide sub-100ms WebSocket connections with no cold start penalty.
Options
Section titled “Options”| Flag | Description |
|---|---|
--platform | Target platform: docker, render, fly, railway, or cloudflare (default: docker) |
--workers | Number of worker processes — not applicable to cloudflare (default: 1) |
--redis | Include Redis/Valkey KV store in deployment config — not applicable to cloudflare |
--out-dir | Output directory for generated files (default: .) |
Use --redis --workers 4 to generate configs pre-configured for multi-worker scaling. See Horizontal Scaling for details.
The command validates your project before generating configs. If pyproject.toml or uv.lock is missing, you’ll see a warning.
Non-Interactive Server Mode
Section titled “Non-Interactive Server Mode”For environments that can’t maintain persistent WebSocket connections — serverless functions, edge workers, or deployments that need simple horizontal scaling — PyWire offers a non-interactive HTTP-only mode.
from pywire import PyWire
app = PyWire(interactive_server_mode=False)How It Works
Section titled “How It Works”When interactive_server_mode=False:
- No WebSocket endpoint is registered. No persistent connections.
- SPA navigation uses
fetch()— the client fetches new page HTML via HTTP and applies it with morphdom (same smooth transitions). - Session state is persisted via a signed httponly cookie and the session store (in-memory or Redis). State survives across requests.
@submithandlers work via standard form POST. The server calls the handler, re-renders the page, and returns HTML.@click,@input, etc. are silently inactive — a dev-mode console warning lists which handlers won’t work.
When to Use It
Section titled “When to Use It”| Scenario | Mode |
|---|---|
| Traditional server deployment (VPS, containers) | interactive_server_mode=True (default) |
| Serverless functions (AWS Lambda, Vercel) | interactive_server_mode=False |
| Edge workers (Cloudflare Workers) | interactive_server_mode=False |
| Horizontal scaling without Redis | interactive_server_mode=False |
| Static/content sites with forms | interactive_server_mode=False |
Session Configuration
Section titled “Session Configuration”In non-interactive mode, sessions use the same Redis/memory store as interactive mode. For multi-worker deployments, use Redis:
export REDIS_URL=redis://localhost:6379app = PyWire(interactive_server_mode=False)# Redis detected automatically from REDIS_URLWith workers=1 and the default in-memory store, sessions work without Redis — similar to a Django/Rails setup.
Preparing for Production
Section titled “Preparing for Production”-
Build artifacts: Run
pywire buildto compile.wirefiles into optimized Python bytecode.Terminal window pywire build --optimize -
Environment variables: Configure database connection strings, API keys, and other secrets via environment variables or a
.envfile. -
Start the server: Use
pywire runfor a production-ready Uvicorn-based server.Terminal window pywire run main:app --host 0.0.0.0 --port 8000 --workers 4
Production Flags
Section titled “Production Flags”| Flag | Description |
|---|---|
--host 0.0.0.0 | Bind to all interfaces (required for containers) |
--port 8000 | Set the port (default: 8000) |
--workers N | Number of worker processes (default: auto based on CPU cores) |
--no-access-log | Disable access logging for better performance |
Manual Deployment
Section titled “Manual Deployment”If you prefer to write deployment configs by hand or use a platform not supported by pywire deploy, PyWire works with any ASGI-compatible hosting.
Docker
Section titled “Docker”Build and run locally:
docker build -t my-pywire-app .docker run -p 8000:8000 my-pywire-appGeneric ASGI Deployment
Section titled “Generic ASGI Deployment”Since PyWire is a standard ASGI application, you can use any ASGI server:
# Uvicorn directlyuvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# Hypercornhypercorn main:app --bind 0.0.0.0:8000 --workers 4SSL / HTTPS
Section titled “SSL / HTTPS”For development with HTTPS, use the SSL flags on pywire dev:
pywire dev --ssl-keyfile key.pem --ssl-certfile cert.pemIn production, terminate SSL at a reverse proxy (Nginx, Caddy, or your cloud provider’s load balancer) rather than at the application level.
Static Files
Section titled “Static Files”PyWire automatically serves files from the static/ directory at the /static URL prefix. In production, consider serving static files from a CDN or reverse proxy for better performance.