A production-ready Axum service is predictable under failure, observable, and secure by default. Apply together with axum and, in the general go-live, production-readiness. Logs/spans: tracing. Runtime: tokio. Client: reqwest.
Boot and configuration
- Load config from env (and files if the project uses them) once at startup.
- Validate with types (
figment,config,envy, or manual parse) — fail fast if a secret/DB URL is missing. - Separate
development/staging/production(logs, auto-migrate, error verbosity). - Do not read env inside handlers.
APP_ENV=production
DATABASE_URL=...
RUST_LOG=info,tower_http=info,my_app=info
LISTEN_ADDR=0.0.0.0:8080
Process and shutdown
- Explicit bind (
0.0.0.0in container; localhost only in dev if desired). - Graceful shutdown on SIGTERM/SIGINT: stop accepting connections, drain requests, close pool.
- Defined shutdown timeout (e.g., 30s); log what did not drain.
tokio::spawnof background tasks with cancellation on shutdown (CancellationToken/watch/AbortHandle).
Health and readiness
| Endpoint | Meaning |
|---|---|
/health or /live |
Process up |
/ready |
Critical dependencies ok (DB ping) |
- Liveness must not fail just because the DB is slow (avoids kill loop), unless the orchestrator policy says otherwise.
- Readiness fails if it cannot serve traffic.
- Does not require auth; does not leak internals.
Timeouts, limits, and resilience
TimeoutLayerglobal or per route group.- Body limit (
DefaultBodyLimit/RequestBodyLimitLayer). - Timeouts on all external HTTP/DB clients.
- Retries only on idempotent transient errors, with backoff and jitter.
- Circuit breaker / degradation when the domain requires it.
- DB pool:
max_connectionsaligned with CPU/latency; do not blindly copy defaults in multi-replica.
Observability
tracing+tracing-subscriber(JSON in production) — details in the skilltracing.TraceLayeroftower-httpwith request id / correlation id.- Propagate
traceparent/ request id if the ecosystem uses it. - Metrics: latency, error rate, saturation (Prometheus via
metricsor the project's standard). - Spans in relevant handlers/services — without logging body with PII/secrets.
- 5xx errors: log with cause; external response generic.
Security
- TLS at the edge (proxy) or in the process if that is the deploy standard.
- Explicit CORS: origins allowlist; never
*with credentials. - Headers:
Strict-Transport-Security,X-Content-Type-Options, frame policy according to the app. - Cookies:
HttpOnly,Secure,SameSiteappropriate. - Auth: JWT/session validated in extractor; authz per resource.
- Secrets only in env/secret manager;
cargo audit/ SCA in CI. - Rate limit on login and expensive endpoints (middleware or API gateway).
HTTP and contracts
- Stable error JSON:
{ "code", "message", "request_id"? }. - Correct status codes; do not mask 500 as 200.
- Pagination with a cap; reject
limitabusive. - Uploads: type/size validated; store outside ephemeral disk without a plan.
Database and migrations
- Migrations in the pipeline before releasing traffic (job or controlled init).
- Avoid
migrate on bootin multi-replica without lock/leadership. - Runtime connections + statement timeouts when the driver allows it.
- Documented rollback/forward strategy for destructive changes.
Container and deploy
- Docker multi-stage: build
cargo build --release, distroless/slim runtime with certs. - Non-root user; static binary or minimal deps.
WORKDIR,EXPOSE,STOPSIGNAL SIGTERM.- Orchestrator healthcheck pointing to
/readyor/health. - Resources: CPU/memory requests/limits; graceful period > shutdown timeout.
- Build: Cargo registry cache in CI;
Cargo.lockcommitted in bins.
Production performance
- Release builds (
--release); LTO/codegen only with evidence. - Avoid unnecessary hot allocations; measure with profiling under load.
- HTTP compression if CPU is to spare and the payload is large.
- Keep-alive connections behind the proxy; HTTP/2 when the edge supports it.
Go-live checklist
- Typed config; boot fails if invalid
- Graceful shutdown tested (SIGTERM)
-
/healthand/readycorrect - Timeouts + body limits active
- JSON tracing + request id
- Basic metrics/alerts
- CORS/headers/authz reviewed
- Migrations in the pipeline
- Non-root image;
cargo auditclean enough - Post-deploy smoke test and rollback plan
Anti-patterns
RUST_LOG=debugin production with PII- Uncaught panics bringing down the process during request handling (prefer
Result) - A single instance with in-memory state without sticky/session store
- Opening CORS to the world to “test quickly” and forgetting
- Deploy without readiness (traffic to an app still migrating)
Completion criteria
- Service starts, serves health/ready and shuts down cleanly with SIGTERM
- Dependency failures appear in logs/metrics, not as silence
- Hardened HTTP surface (limits, timeouts, CORS, authz)
- Defined migrate + deploy + smoke pipeline
- No secrets in the artifact or logs