Deployment¶
You are in the right place if you are shipping FluxLit with Docker or Kubernetes, wiring health checks, or choosing between fluxlit dev and fluxlit run.
FluxLit serves one public port: the ASGI gateway (Uvicorn) proxies API traffic to FastAPI and everything else to a Streamlit subprocess. Point browsers and load balancers at that port only; see Architecture for the request path.
Production entrypoint¶
Use fluxlit run (no reload). Typical container command:
fluxlit run app:app --host 0.0.0.0 --port 8000
The target (module:attr) resolves the same way as fluxlit dev: CLI argument → fluxlit.toml / [tool.fluxlit] target → app:app. Bind address and port also follow Configuration precedence (CLI → env → project file → defaults).
Behind a reverse proxy, pass --proxy-headers (or set FLUXLIT_TRUST_PROXY=1) and configure FLUXLIT_ROOT_PATH when the app is mounted under a subpath — see the reverse-proxy section in Configuration. For a multi-segment public path such as /apps/my-app, nginx Compose, smoke checks, and TLS/trust notes, see Path-prefixed mount (/apps/my-app) in Production TLS and edge headers and docker/proxy-deployment/docker-compose.apps-prefix.yml in the repository.
Running under Uvicorn directly¶
The usual pattern matches FastAPI: your FluxLit instance is the ASGI app.
uvicorn app:app --host 0.0.0.0 --port 8000
Set target in fluxlit.toml (or FluxLit(import_target=...), or FLUXLIT_APP) when
the import path is not app:app. Set gateway_port in fluxlit.toml (or
FLUXLIT_GATEWAY_PORT) to match --port when it is not 8000.
Legacy / factory entrypoint (same stack, requires env):
export FLUXLIT_APP="app:app"
uvicorn fluxlit.runtime:create_unified_app --factory --host 0.0.0.0 --port 8000
Notes:
Uvicorn
--workers> 1 is not supported for the unified stack; lifespan startup fails if Uvicorn’s worker count is greater than one (unlessFLUXLIT_ALLOW_UNIFIED_UVICORN_MULTIWORKER=1, which is explicitly unsupported). Prefer one process per replica — see Scaling and workers below.Lifespan follows the ASGI spec; the inner FastAPI app’s lifespan runs after the Streamlit sidecar starts.
Health checks¶
Probe |
Path |
Meaning |
|---|---|---|
Liveness |
|
FastAPI app is up (does not check Streamlit). |
Readiness |
|
When the unified runtime has configured a Streamlit upstream ( |
Both routes are hidden from OpenAPI so they do not clutter /api/docs. Use them in Kubernetes livenessProbe / readinessProbe, load balancers, or Compose healthcheck curls.
Example checks:
curl -fsS http://127.0.0.1:8000/api/healthz
curl -fsS http://127.0.0.1:8000/api/readyz
Kubernetes: graceful shutdown¶
FluxLit runs Uvicorn and a Streamlit child in one pod. When Kubernetes sends SIGTERM, Uvicorn stops accepting new connections and drains in-flight HTTP/WebSockets up to timeout_graceful_shutdown, then runs ASGI lifespan shutdown (which tears down Streamlit). Align timeouts so the pod is not SIGKILL mid-drain.
FLUXLIT_UVICORN_GRACEFUL_SHUTDOWN_TIMEOUT_S— optional; forwarded to Uvicorn astimeout_graceful_shutdown. Set it belowterminationGracePeriodSeconds, leaving time forpreStophooks and for the runtime’s bounded Streamlit termination (SIGINT / terminate / kill sequence) after lifespan exits.terminationGracePeriodSeconds— must exceed Uvicorn’s graceful window plus anypreStopsleep you add for load balancers to stop sending traffic before SIGTERM.preStop— common pattern:sleep 5(or similar) so endpoints update before the main process sees SIGTERM; alternative is active coordination with your ingress. This is not a substitute for Uvicorn drain; it only reduces in-flight work at cutoff.
Ordering: ingress / kube-proxy stop sending new connections → SIGTERM → Uvicorn drain → lifespan stops Streamlit → process exits. If drain is too long, Kubernetes still SIGKILL after the grace period.
Docker and Compose¶
fluxlit buildwrites a minimalDockerfileand.dockerignoreinto the current directory (or-o/--output). Adjust the generated files for your dependency layout, base image digest, non-root user, and image size. The template uses a digest-pinnedpython:3.12-slimbase, runs asappuser,CMD ["fluxlit", "run", "<target>"], and setsFLUXLIT_GATEWAY_HOST=0.0.0.0. Refresh theFROM python@sha256:…line when you intentionally upgrade the base image (matchdocker pull python:3.12-slimthen inspect RepoDigests).Production images should install from a committed lockfile (
pip-tools/uv, etc.) your app controls;fluxlit buildstays minimal on purpose.A runnable Compose example lives in the repository at
examples/docker_compose/(requirements.txtfrompip-compile, exposes port 8000).For nginx, TLS, and subpath smoke tests, see
docker/proxy-deployment/in the repo.
Do not run fluxlit dev with --reload in production images.
For TLS termination, HSTS, forwarded header trust, and CSP guidance, see Production TLS and edge headers. For secrets, logs, and JWT/OIDC rotation, see Secrets lifecycle.
Reverse proxy compatibility matrix¶
Runnable smoke stacks live under docker/proxy-deployment/ in the repository (run-all-proxy-smokes.sh runs them sequentially in CI-like automation).
Stack |
Public port |
Path mode |
Edge |
WebSocket notes |
|---|---|---|---|---|
nginx strip-prefix (default compose) |
8080 |
|
nginx 1.27 |
|
nginx root (full URL path) |
8082 |
No strip; origin path matches app |
nginx 1.27 |
Same as strip-prefix |
nginx full-path |
8081 |
Proxy passes full browser path |
nginx 1.27 |
Match |
nginx |
8083 |
Multi-segment prefix; see Path-prefixed mount (/apps/my-app) in Production TLS and edge headers |
nginx 1.27 |
Same Upgrade map |
HTTPS + nginx |
8444 |
Strip-prefix with TLS |
nginx + test certs |
Use |
Caddy strip-prefix |
8084 |
|
Caddy 2.8 |
Third engine; same strip-prefix contract |
Traefik strip-prefix |
8085 |
|
Traefik 3.2 |
File provider; same contract as nginx strip-prefix |
Caddy uses docker-compose.caddy.yml merged with the base docker-compose.yml (FluxLit service unchanged). Run:
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d --build
PUBLIC_PREFIX=/myapp BASE_URL=http://127.0.0.1:8084 ./smoke-test.sh
Traefik uses docker-compose.traefik.yml merged with the base file (strip-prefix via file provider). Run:
docker compose -f docker-compose.yml -f docker-compose.traefik.yml up -d --build
PUBLIC_PREFIX=/myapp BASE_URL=http://127.0.0.1:8085 ./smoke-test.sh
Observability in production¶
Enable
FLUXLIT_ENABLE_GATEWAY_ACCESS_LOG=1only if your log pipeline can handle per-request volume; pair with filters andfluxlit.logging.redactwhere headers are copied into logs — Observability.For JSON lines (Loki, Datadog, Cloud Logging), attach
JsonLogFormatterto your root,uvicorn, andfluxlitloggers (sampledictConfigin Observability).FLUXLIT_ENABLE_REQUEST_LOGGINGaffects the inner FastAPI app only (not the gateway dispatch line).
Runtime-injected environment¶
The parent process sets variables for the Streamlit child and for gateway code that reads upstream state. You normally do not set these by hand when using fluxlit run; see Runtime-managed environment variables.
Scaling and workers¶
Single process (default)¶
One Uvicorn worker is the supported model: one gateway ASGI app and one Streamlit child share loopback and process-local state (upstream URL file, OIDC BFF in-memory stores, etc.).
Uvicorn
--workers> 1 is not supported for this unified stack in one OS process: extra workers would each try to own a Streamlit subprocess and shared resources would diverge. Do not enable multi-worker on one pod to “use all CPUs”; scale out instead (see below). Starting with recent FluxLit releases, unified ASGI lifespan startup fails fast when Uvicorn’sConfig.workersis greater than one (unless you setFLUXLIT_ALLOW_UNIFIED_UVICORN_MULTIWORKER=1, which keeps the layout explicitly unsupported).
Horizontal scale (multiple replicas)¶
Behind a Layer 7 load balancer or Kubernetes Service with multiple endpoints:
Each replica runs its own gateway + Streamlit pair. Streamlit’s default session is tied to the server-side script run and WebSocket; after a hard refresh or new connection, users may land on a different replica and see a new session unless you add affinity.
Sticky sessions (session affinity / cookie-based or IP-hash) route the same browser to the same replica for a period. That improves continuity for interactive UIs but is not a full multi-replica session store: long-lived affinity tables, draining nodes, and failures still drop local state.
When to add an external session store: if you need consistent application state across replicas without sticky sessions (or in addition to them), persist state outside the process (database, Redis, etc.). FluxLit’s URL-session helpers provide the cookie-free binding pattern; production multi-replica continuity still depends on the store you choose. See URL session continuity (no cookies) for
SessionStorerecipes and External store recipes.
Multi-replica operations checklist¶
Use this when replicas > 1 (Kubernetes) or multiple VMs/containers sit behind one load balancer:
Decision |
Guidance |
|---|---|
Sticky sessions |
Prefer cookie-based or connection affinity from your ingress/LB when you need Streamlit’s in-process |
OIDC BFF / in-memory auth |
The bundled BFF patterns use process-local state for some flows; multiple replicas require you to externalize |
URL-session continuity |
|
Rollouts |
Keep readiness on |
Observability |
Correlate gateway |
Rollout and drain playbook¶
For multi-replica deployments:
Run one FluxLit process per replica. Do not use Uvicorn worker fan-out inside a replica.
Use readiness (
/api/readyz) to remove a replica before it receives user traffic.Give Streamlit WebSockets time to close during rollouts by aligning
preStop,terminationGracePeriodSeconds, andFLUXLIT_UVICORN_GRACEFUL_SHUTDOWN_TIMEOUT_S.If users must survive replica replacement or non-sticky routing, store continuity state in an external
SessionStore; in-memory stores are per replica.Keep sticky sessions as a routing optimization, not as the only persistence layer for important app state.
Supported alternatives to multi-worker¶
One process per replica (Kubernetes Pod, ECS task, VM): scale replica count; tune CPU for a single process.
Split topology (advanced): run Streamlit and the API on different hosts and point
FLUXLIT_STREAMLIT_UPSTREAMat the Streamlit origin—only if your platform requires separate services and you accept operational complexity.Multiple Uvicorn workers for API-only does not apply to
FluxLitunified mode; use plain FastAPI + separate Streamlit hosting if you truly need multi-worker HTTP for the API only.
Ingress engines: affinity, WebSockets, strip-prefix (pointers)¶
Engine |
Affinity / stickiness |
WebSockets / |
Strip-prefix vs full path |
|---|---|---|---|
nginx |
|
Pass |
References: |
Traefik |
Sticky sessions on routers/services (cookie-based); see Traefik version docs. |
Treat WebSocket like HTTP Upgrade; avoid buffering middleware on WS routes. |
Repo: |
Caddy |
|
Tune flush / timeouts for long streams; see Caddy compose under |
Align |
Always match FLUXLIT_TRUST_PROXY, FLUXLIT_ROOT_PATH, and FLUXLIT_PUBLIC_BASE_URL to what the edge forwards — details in Production TLS and edge headers.
Reference: Kubernetes¶
A minimal Deployment + Service that matches the hardened image contract (probes, graceful shutdown, non-root) lives in examples/kubernetes/ in the repository. Copy and adjust image name, resources, and ConfigMap / Secret wiring for your cluster.
Checklist¶
[ ]
fluxlit doctorpasses (or only acceptable WARNs).[ ] Optional CI gate:
fluxlit doctor app:app --jsonis parsed and checked for unexpectedFAILdiagnostics.[ ]
FLUXLIT_GATEWAY_HOST/ bind address matches container/platform (often0.0.0.0).[ ] Proxy:
FLUXLIT_TRUST_PROXY,FLUXLIT_ROOT_PATH, andFLUXLIT_PUBLIC_BASE_URL(for OAuth) set correctly.[ ] Readiness probe uses
/api/readyzwhen Streamlit must be up before receiving traffic.[ ] Optional: set
FLUXLIT_UVICORN_GRACEFUL_SHUTDOWN_TIMEOUT_S(and KubernetesterminationGracePeriodSeconds/preStop) per Kubernetes: graceful shutdown above.[ ] Secrets in env or a secrets manager — not baked into images;
.envexcluded from Docker context (default.dockerignorefromfluxlit buildalready ignores.env). See Secrets lifecycle.[ ]
FLUXLIT_FORWARDED_ALLOW_IPStightened whenFLUXLIT_TRUST_PROXYis on (not*in untrusted networks). See Production TLS and edge headers.[ ] For Kubernetes: start from
examples/kubernetes/and align probes andterminationGracePeriodSecondswith Kubernetes: graceful shutdown.