Deployment
Tenchi produces an ordinary ASGI application. Production readiness comes from explicit lifecycle ownership, middleware, health checks, observability, and a repeatable contract gate.
Start with the production handbook when the application still needs configuration, transaction, retry, worker, or telemetry decisions. This page covers the final process and release boundary.
Run an ASGI server
The generated application exposes app.server.asgi:app:
uv run uvicorn app.server.asgi:app \
--host 0.0.0.0 \
--port 8000 \
--proxy-headersUse your platform's process manager, worker count, graceful shutdown, and
request-timeout guidance. tenchi dev enables reload and is not the production
entrypoint.
Own resources with lifespan
Open database pools, SDK clients, and other process-scoped resources in the application lifespan. Yield a small state object and build each request context from it. Cleanup runs during graceful shutdown.
Use a request-scoped context manager for transactions or ports whose success
depends on the request outcome. Application exceptions pass through its
__aexit__ before Tenchi maps them to HTTP, allowing commit-on-success and
rollback-on-error behavior.
Load validated configuration before opening resources. Put database migrations in a controlled release step; use lifespan to open pools and verify the schema generation that the process expects.
Add ASGI middleware directly
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
middleware = [
Middleware(
TrustedHostMiddleware,
allowed_hosts=["api.example.com"],
),
Middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_methods=["*"],
allow_headers=["authorization", "content-type"],
),
]
app = create_app(
routes=routes,
context_factory=create_context,
lifespan=lifespan,
middleware=middleware,
)Configure HTTPS and forwarded headers at the trusted reverse proxy boundary. Keep host and origin allowlists explicit.
The generated Swagger UI loads pinned CDN assets with subresource integrity.
For an offline deployment, self-host the assets and pass their URLs to
swagger_ui_route(). A content-security policy must also allow the page's
fixed inline initializer and style, normally through their hashes. Protect the
UI and openapi_route() together when the API description is not public.
Health and readiness
from tenchi.health import health_route
health = health_route(
path="/health",
checks={"database": check_database},
check_timeout=2.0,
)Asynchronous health checks run concurrently and return 503 when any check is
unhealthy. The route is public by default; pass public=False when
infrastructure can authenticate probes.
Limits and deadlines
The server limits request bodies to 1 MiB by default. Set
max_request_bytes= globally or on an individual contract. Declare
timeout= for operations that need an application-level deadline, and also
configure transport and platform timeouts.
Observe outcomes
RequestInfo includes a trusted request ID. Tenchi accepts a valid inbound
x-request-id or creates one and includes it on responses. Outcome observers
receive the request, final status, duration, and application/framework error
source after request-scoped cleanup.
Use request and use-case observers or ASGI middleware to feed structured logs, metrics, and traces. They should never contain request bodies, credentials, or error details unless the application deliberately redacts them.
See Observability and audit for label, correlation, worker, and redaction conventions.
Release sequence
Run the local application gate and compare the API with the version currently deployed:
uv run tenchi check
uv run tenchi openapi --routes app.server.routes:api_routes \
--title my_app --diff-ref "$BASE_SHA" --snapshot openapi.json
uv run tenchi tools \
--diff-ref "$BASE_SHA" --snapshot tools.jsontenchi check includes the exact OpenAPI and application-tool snapshot checks.
The separate historical compatibility commands remain necessary because they
compare the working contracts with baselines from before the deployment
change.
Then release in this order:
- Verify production settings and secret references without printing their values.
- Back up data according to the datastore's recovery plan.
- Run backward-compatible database migrations once.
- Start the new API generation without sending it traffic.
- Run
tenchi preflightagainst the target environment. - Wait for startup and readiness checks, then shift traffic gradually.
- Start or update background workers that are compatible with both application generations.
- Smoke-test a public route and an authenticated operation through the production edge.
- Watch error rate, latency, database saturation, and queue age during the rollout.
tenchi check does not contact production dependencies, validate secret
manager permissions, run migrations, or prove that an external worker is
healthy. Keep migrations as their own release step, then use
tenchi preflight for read-only, timeout-bounded dependency
checks where the target environment is available.
Plan rollback before rollout. Application rollback must remain compatible with the migrated schema and any work records already written by the new generation.
Raw httpx.ASGITransport does not run lifespan by itself. Prefer
tenchi.testing.open_client() or open_http(), which start and stop the
application correctly.