Deployment Overview
Ultimo compiles to a single static binary — no runtime, no interpreter, no
node_modules. This makes deployment straightforward on any platform that runs
Linux containers or bare-metal binaries.
Build for release
cargo build --releaseThe optimized binary lands at target/release/<your-app> (typically 10–15 MB).
Copy it anywhere and run it — zero dependencies beyond libc.
Guides
| Platform | Best for |
|---|---|
| Docker | Reproducible builds, any hosting |
| Fly.io | Global edge deployment, simple CLI |
| Railway | Git-push deploys, zero config |
| AWS | Enterprise, ECS/Fargate |
| Google Cloud Run | Serverless containers, scale-to-zero |
| Azure | Container Apps, enterprise Azure |
| DigitalOcean | App Platform, simple PaaS |
| Kubernetes | Full orchestration, multi-cloud |
Production checklist
Before going live:
- Health check endpoint — add a
GET /healthroute returning 200:
app.get("/health", |ctx: Context| async move {
ctx.response().text("ok")
});- Environment variables — don't hardcode secrets; use env vars or a secret manager
let db_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");-
Logging — set
RUST_LOG=info(orRUST_LOG=ultimo=info,my_app=debug) -
HTTPS — terminate TLS at the load balancer/reverse proxy (Caddy, nginx, cloud LB) — Ultimo serves HTTP, your infra adds the S
-
Security headers — enable the built-in middleware:
app.use_middleware(ultimo::middleware::builtin::security_headers());- Trust proxy — if behind a load balancer, enable so
client_ip()readsX-Forwarded-For:
app.trust_proxy(true);- IP filtering — restrict access if needed:
use ultimo::middleware::builtin::IpFilter;
app.use_middleware(IpFilter::allow(&["10.0.0.0/8"]).build());-
Graceful shutdown — Ultimo handles
SIGTERMautomatically; HTTP connections drain, WebSocket clients get close frames viabroadcast_all() -
Resource limits — Ultimo binaries are lean; 64–128 MB RAM handles most workloads; set container limits accordingly
Environment variables
Ultimo apps typically read configuration from environment variables. Here's a common pattern:
use std::env;
let port: u16 = env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.expect("PORT must be a number");
let host = env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string());