Deployment: Containers, TLS, and safe releases
This guide walks from running Stario with the stock CLI to a minimal container and a TLS front so you can ship without inventing a bespoke stack on day one.
Mental model: Stario listens for plain HTTP on the address you configure. TLS termination (HTTPS to clients, HTTP to the app) and trust headers live at the edge (Caddy, nginx, a load balancer). You still point stario serve at the same MODULE:bootstrap and the same in-process request limits as in staging—only the network path changes.
Stario is not ASGI: there is no uvicorn main:app-style ASGI callable in the framework. You run the Stario CLI (stario serve / stario watch) or construct Server in code—the built-in asyncio HTTP/1.1 stack, not Hypercorn/Uvicorn workers.
1. Start with stario serve and a tracer
The supported path is the same entry you use in development: stario serve MODULE:bootstrap. The CLI builds Server, runs your bootstrap, and blocks until shutdown.
Pick the tracer with STARIO_TRACER:
json— NDJSON lines on stdout (typical in containers and log aggregators; optionalSTARIO_TRACERS_JSON_*tuning whenSTARIO_TRACER=json).sqlite— local SQLite for ad hoc inspection (STARIO_TRACERS_SQLITE*optional overrides; see Getting insights from SQLite tracer).tty— interactive span tree on a terminal (fine on a laptop; rarely what you want in production).auto, or omitSTARIO_TRACER— TTY span tree when stdout is a TTY, otherwiseJsonTracer(what you usually get under Docker without a TTY).
You can also pass a custom callable as module:callable (zero-argument function returning a Tracer) — see Telemetry — Custom tracers.
STARIO_HOST=127.0.0.1 STARIO_PORT=8000 STARIO_TRACER=json stario serve main:bootstrapFor day-to-day local work, stario watch is still the right loop; for anything you intend to release, prove the same bootstrap under serve with the tracer you plan to run in production (often json or a custom sink, not tty).
Run stario serve --help for the full STARIO_* list (listen, compression, request limits, graceful shutdown). Stario does not load .env files.
2. Why TLS lives in front of Stario
Stario’s HTTP stack serves plain HTTP/1.1 on the listen socket you configure. It does not terminate TLS for you: no automatic HTTPS, no certificates inside the framework. TLS termination means decrypting HTTPS from browsers at the edge and speaking plain HTTP to Stario on a trusted path. That usually belongs at the edge (Caddy, nginx, a cloud load balancer) or on a sidecar.
Put another way: run Stario behind something that speaks HTTPS to clients and forwards HTTP to your process (or connect over a Unix socket on the same host). That edge also gives you stable Host headers, HTTP/2 or HTTP/3 toward the browser while the upstream to Stario stays HTTP/1.1.
3. Minimal Docker image
The shape is: install your app and dependencies, expose the port you bind, and CMD invokes stario serve. Set STARIO_HOST=0.0.0.0 inside the container so traffic from the bridge network reaches the process (the default 127.0.0.1 only accepts local connections).
FROM python:3.14-slim WORKDIR /appCOPY . .RUN pip install uv && uv sync --frozen ENV PYTHONUNBUFFERED=1ENV STARIO_HOST=0.0.0.0ENV STARIO_PORT=8000ENV STARIO_TRACER=json EXPOSE 8000CMD ["uv", "run", "stario", "serve", "main:bootstrap"]Adjust COPY / RUN to match your layout. The important part for safety is: one clear CMD, no shell wrapper unless you need it, and the same MODULE:bootstrap you test with TestClient.
4. Caddy in front (TLS + reverse proxy)
Caddy can obtain and renew certificates automatically. Stario stays on HTTP on localhost or a Docker network; Caddy terminates TLS and reverse_proxy to that upstream.
TCP upstream (Stario listening on 127.0.0.1:8000 on the host or another container):
example.com { reverse_proxy 127.0.0.1:8000}Unix socket upstream (Stario started with STARIO_UNIX_SOCKET=/run/stario/app.sock — see Server):
example.com { reverse_proxy unix//run/stario/app.sock}Tune proxy timeouts if you use long-lived streams (SSE, Datastar)—aggressive idle cuts break long GET streams. In-process graceful shutdown drain is STARIO_GRACEFUL_SHUTDOWN_TIMEOUT (default 5 seconds; total shutdown may exceed that by up to ~1 s during force-close — see Runtime — Shutdown sequence).
5. Request limits on the Stario side
The HTTP stack enforces size caps on every connection—you do not need to reimplement them in each handler.
| Control | Environment variable | Default (order of magnitude) | On violation |
|---|---|---|---|
| Request line + headers (total) | STARIO_REQUESTS_MAX_HEADER_BYTES | 64 KiB | 431 Request Header Fields Too Large |
| Headers read stall | STARIO_REQUESTS_HEADER_TIMEOUT | 5 s | Connection closed (no HTTP status) |
| Request body (total bytes) | STARIO_REQUESTS_MAX_BODY_BYTES | 10 MiB | 413 Payload Too Large |
| Body read stalls between chunks | STARIO_REQUESTS_BODY_TIMEOUT | 30 s | 408 Request Timeout (when handler reads body) |
| Keep-alive idle | STARIO_REQUESTS_KEEP_ALIVE_TIMEOUT | 5 s | Connection closed |
| Pipelining queue depth | STARIO_REQUESTS_MAX_PIPELINED_REQUESTS | 8 queued (+ 1 in-flight) | 503 Pipeline queue full |
Example:
STARIO_HOST=0.0.0.0 STARIO_PORT=8000 STARIO_TRACER=json \ STARIO_REQUESTS_MAX_HEADER_BYTES=65536 \ STARIO_REQUESTS_MAX_BODY_BYTES=10485760 \ stario serve main:bootstrapResponse security headers (CSP, HSTS, X-Frame-Options, …) are your responsibility in handlers or middleware—Stario does not inject a default security header bundle.
6. Limits, headers, and timeouts on the reverse proxy
Stario does not rewrite Request.host or scheme from X-Forwarded-* automatically—your app sees the Host header the edge sent. Configure proxies so they forward the host and scheme you expect for URL generation and routing.
Putting Caddy (or nginx, Envoy, a cloud load balancer, …) in front is how you add defense in depth: many teams set stricter limits at the edge than inside the app so garbage never reaches Python.
Request body size — Caddy can cap bodies before they hit Stario (
request_bodywithmax_size, e.g.10MB). Alignmax_sizewithSTARIO_REQUESTS_MAX_BODY_BYTES.Timeouts — Configure read, write, and idle timeouts on
reverse_proxy/ transport. Keep SSE/Datastar routes in mind: longGETstreams need generous read-side timeouts.Security headers at the edge — You can set
Strict-Transport-Security,X-Content-Type-Options,Referrer-Policy, and related headers in Caddy’sheaderdirective.
7. What is not built in (rate limits, DDoS, WAF)
Stario is an application server, not an edge firewall. By default there is no request rate limiting, per-IP quotas, bot detection, or DDoS mitigation in the framework—those belong in front of Stario (Caddy modules or plugins, your CDN / WAF, cloud load balancer rules, or a dedicated reverse proxy tier).
8. Configuration and environment
Stario does not mandate a single config file format. Common patterns:
App environment — read in
bootstrap(os.environ,pydantic-settings, etc.) for database URLs, API keys, and feature flags. Pass them withdocker run -e, Composeenvironment:, or your orchestrator’s secrets.Server runtime (
STARIO_*) — host, port, tracer, loop, Unix socket (STARIO_UNIX_SOCKET,STARIO_UNIX_SOCKET_MODE), listen backlog (STARIO_BACKLOG), graceful shutdown (STARIO_GRACEFUL_SHUTDOWN_TIMEOUT), request limits, compression. See Configuration andstario serve --help.Bootstrap — keep secrets and clients in
bootstraplifetime; avoid globals that ignore configuration.
Document the exact stario serve … command and STARIO_* values next to your image tag so releases are reproducible.
Related
Configuration — full
STARIO_*reference.Runtime — Server — bind addresses, graceful shutdown,
Server.Getting insights from SQLite tracer — SQL over
SqliteTraceroutput.Mapping errors to HTTP responses — stable HTTP mapping in production.
Authentication with cookie sessions — cookies behind HTTPS.