#Deployment Guide

Version: 0.33.0 Updated: 2026-03-15 Applies to: ranvier, ranvier-runtime, ranvier-http Category: Operations


This guide covers containerized deployment of a Ranvier application β€” build strategy, Docker multi-stage, Kubernetes manifests, reverse proxy, and environment configuration.

#1. Build Strategy

Ranvier applications compile to a fully-static musl binary for production. This eliminates OS-level dependencies and enables FROM scratch runtime images.

Build Mode Target Use Case
Native debug host target Local iteration
Native release host target Local integration test
musl release x86_64-unknown-linux-musl Container / production
# Add musl target (Linux)
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

On Windows/macOS, use the Docker builder stage for musl compilation (see Β§2).


#2. Docker Multi-Stage Build

# ---- Builder ----
FROM clux/muslrust:stable AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./

# Dependency caching: build dummy binary first
RUN mkdir -p src && echo 'fn main(){}' > src/main.rs
RUN cargo build --release --target x86_64-unknown-linux-musl && \
    rm -rf src target/x86_64-unknown-linux-musl/release/deps/my_app*

COPY src ./src
RUN cargo build --release --target x86_64-unknown-linux-musl

# ---- Runtime ----
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-app /app
EXPOSE 3000
ENTRYPOINT ["/app"]

Key decisions:

Choice Rationale
clux/muslrust:stable Full Rust + musl toolchain
FROM scratch Zero OS surface, minimal footprint
rustls only No OpenSSL dependency β€” works in scratch
CA certs copy Required for outbound HTTPS connections

#3. Kubernetes Manifests

#Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ranvier-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ranvier-app
  template:
    metadata:
      labels:
        app: ranvier-app
    spec:
      containers:
        - name: app
          image: my-registry/ranvier-app:latest
          ports:
            - containerPort: 3000
          env:
            - name: RUST_LOG
              value: "info"
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: ranvier-secrets
                  key: jwt-secret
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 256Mi

#Service

apiVersion: v1
kind: Service
metadata:
  name: ranvier-app
spec:
  selector:
    app: ranvier-app
  ports:
    - port: 80
      targetPort: 3000
  type: ClusterIP

#HPA (Horizontal Pod Autoscaler)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ranvier-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ranvier-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

#4. Health Check Endpoint

use ranvier::prelude::*;

#[transition]
async fn health_check(
    _state: (),
    _resources: &(),
    _bus: &mut Bus,
) -> Outcome<String, String> {
    Outcome::Next(r#"{"status":"ok"}"#.to_string())
}

// Mount in your Axon setup
let health = Axon::simple::<String>("health")
    .then(health_check);

Ranvier::http()
    .bind("0.0.0.0:3000")
    .route("/health", health)
    .route("/api/hello", hello_axon)
    .run(())
    .await?;

#5. Reverse Proxy (Nginx)

Nginx serves static frontend assets and proxies API calls to the Ranvier backend:

server {
    listen 80;

    # Static SPA
    root /usr/share/nginx/html;
    location / { try_files $uri $uri/ /index.html; }

    # API proxy β†’ Ranvier backend
    location /api/ {
        proxy_pass http://backend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Ranvier::http() is an Ingress Builder, not a web server. Using Nginx as the edge preserves the Ranvier application's role as a pure API layer.


#6. Compose Topology

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Browser :8080 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
  β”‚   proxy     β”‚  nginx:alpine β€” :80
  β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”˜
     β”‚       β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚frontendβ”‚ β”‚ backend   β”‚  Ranvier β€” :3000
β”‚(static)β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
           β”‚     db       β”‚  postgres:16-alpine β€” :5432
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Setting Dev Prod
DB port exposed Yes (5432) No
RUST_LOG debug info
restart policy none unless-stopped
JWT_SECRET test value env/secret

#7. Environment Variables

Variable Required Default Description
JWT_SECRET Yes (if auth) β€” JWT signing secret (never hardcode)
DATABASE_URL Yes (if persistence) β€” postgres://user:pass@host:5432/db
RUST_LOG No info Log filter (debug, info, warn, error)
OTEL_EXPORTER_OTLP_ENDPOINT No β€” OTLP collector endpoint
PORT No 3000 HTTP bind port
  • Always use .env.example as the source of truth
  • Never commit .env to version control
  • In production, use Kubernetes Secrets or vault

#8. Release Build Optimization

# Cargo.toml
[profile.release]
lto = true
strip = true
codegen-units = 1
opt-level = 3
cargo build --release --target x86_64-unknown-linux-musl

#9. CI Pipeline (musl build)

name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with: { targets: x86_64-unknown-linux-musl }
      - uses: Swatinem/rust-cache@v2
      - run: cargo check --workspace
      - run: cargo test --workspace
      - run: cargo clippy --workspace -- -D warnings
      - run: cargo build --release --target x86_64-unknown-linux-musl

#10. Deployment Checklist

#Required

  • --release profile with LTO, strip, codegen-units=1
  • Docker multi-stage build (builder β†’ scratch)
  • Health check endpoint (/health) configured
  • Environment variables documented and injected
  • Secrets NOT committed to version control
  • RUST_LOG=info set for production
  • Kubernetes readiness/liveness probes configured
  • HPA for auto-scaling based on CPU
  • OpenTelemetry exporter for observability
  • Nginx reverse proxy for static assets + API routing
  • CI pipeline with musl build verification

  • Production Readiness Checklist β€” pre-deployment checklist
  • Performance Tuning β€” profiling, connection pools, async optimization
  • Security Hardening β€” auth, CORS, error redaction
  • OTel Ops Playbook β€” observability setup