Skip to content

Deployment

Oxide compiles to a single statically-linked binary. Deploy it the same way as any other Rust binary — a Docker container, a bare server, or a Kubernetes deployment. The framework handles SIGTERM cleanly and drains in-flight requests before exiting.

Production knobs

Set these in your deployment’s environment (compose.yml, Kubernetes manifest, systemd unit, etc.). Defaults are sensible but worth reviewing.

Env varDefaultNotes
APP_LISTEN127.0.0.1:8000typically 0.0.0.0:<port> behind a load balancer
APP_BODY_MAX_MB10raise for file-upload endpoints, lower for API-only services
APP_REQUEST_TIMEOUT_SECS30keep below your ingress / load balancer timeout
APP_SHUTDOWN_TIMEOUT_SECS30keep below your orchestrator’s grace period (K8s default is 30s)
DB_CONNECTIONsqliteset mysql in prod
BCRYPT_ROUNDS12OWASP-accepted; don’t lower
RUST_LOGinfoinfo,sqlx=warn in prod to cut the query-log noise

Graceful shutdown

On SIGTERM / SIGINT, Oxide:

  1. Stops accepting new connections.
  2. Tells every in-flight connection to finish its current request.
  3. Waits up to APP_SHUTDOWN_TIMEOUT_SECS for them to drain.
  4. Force-closes anything still outstanding, logs a warning, exits 0.

Works with docker stop, K8s termination, systemd stop, etc.

TLS

Oxide binds plain TCP. Terminate TLS at your load balancer, ingress controller, or reverse proxy (nginx, Caddy, Cloudflare, Traefik). The server itself does not handle TLS.

Docker

A compose.yml is scaffolded with every new project for dev. For prod, build a multi-stage image:

# Build stage
FROM rust:1-slim AS build
WORKDIR /app
COPY . .
RUN cargo build --release
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/target/release/your-app-name /usr/local/bin/app
COPY config/ /app/config/
COPY .env.production /app/.env
EXPOSE 8000
CMD ["app", "serve"]

Replace your-app-name with your binary (matches Cargo.toml’s [[bin]].name).

Health checks

Every scaffolded project ships with:

GET /api/v1/health → 200 "ok"

Wire it to your load balancer’s health-check endpoint.

Migrations on deploy

oxide migrate is idempotent — running it every time the app boots is safe. The default scaffolded main.rs already does that inside serve():

Migrator::up(db.conn(), None).await.expect("auto-migrate failed on boot");

For stricter environments where you want a separate migration step, remove that line from main.rs and run oxide migrate as a deployment job before bringing up the server.

Logs

Oxide uses tracing. Everything’s JSON-friendly with tracing_subscriber::fmt().json():

tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
.json()
.init();

Swap the default init in main.rs for this when you’re ready to ship to a log aggregator.