Ferrum Alloy
A toolkit for Axum services. Connected to the Edge.
A batteries-included toolkit for Rust API services built on Axum. Alloy handles the repetitive production setup (configuration, errors, health, limits, graceful shutdown and request telemetry) while your handlers, extractors, state, routers and Tower middleware stay ordinary Axum.
- Status
- Pre-releaseNot on crates.io; depend on a pinned git revision
- Ships as
- Rust library + CLIFive crates and the
ferrum-alloycommand - Built with
- Axum, Tokio, TowerOptional rustls, SQLx, utoipa, OpenTelemetry
- Ferrum Edge
- Tested with v0.9.10 and v0.9.9Current source pairing; Alloy has no published release
Start a service, or instrument the one you have
A new service is one builder call around an ordinary Axum router. An existing Axum app adds only the
ferrum-alloy-telemetry Tower layer and keeps its own runtime, subscriber, router and server.
- New service: run
ferrum-alloy new orders-api, or useAlloyAppdirectly - Existing Axum app: add the telemetry layer, change nothing else
- Escape hatch:
into_parts()hands back the composed router to serve however you like
use axum::{Router, routing::get};
use ferrum_alloy::AlloyApp;
async fn hello() -> &'static str {
"Hello from Ferrum Alloy"
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let router = Router::new().route("/hello", get(hello));
AlloyApp::new("hello-api").router(router).run().await?;
Ok(())
}
examples/minimal service. Health, limits, Problem Details, telemetry and draining come with it.The Production Setup, Done Once
Everything a Rust API service needs before its first real request, without replacing the Axum you already know.
Typed configuration
Strict and typed. Precedence is builder, then FERRUM_ALLOY_*, then TOML, then defaults. Secrets are redacted and errors never quote configuration values.
RFC 9457 errors
Problem Details for framework errors and for Problem-returning Json, Path, Query and ValidJson extractors. Application bodies are never rewritten.
Health and draining
Minimal liveness, cached single-flight readiness and a draining state. Detailed health sits on a token-protected management listener.
Limits and lifecycle
Body, header, connection and admission limits. A time-to-headers deadline that never cuts SSE streams. SIGTERM draining with a budget and forced close.
Truthful telemetry
Request ids and route-template metrics. W3C trace context is accepted only from trusted transport peers, and accounting ends exactly once, when the response body ends.
Optional batteries
Cargo features for OTLP export, rustls with verified client identity, Ferrum Edge trust, PostgreSQL, OpenAPI with a protected Swagger UI, JWT/JWKS, an HTTP client, compression and CORS.
What the Minimal Service Already Does
With no configuration at all, the five-line service above provides these surfaces.
| Surface | Default |
|---|---|
| Application listener | 127.0.0.1:8080. Bind 0.0.0.0:8080 explicitly in containers with FERRUM_ALLOY_BIND. |
| Management listener | 127.0.0.1:9090 with /livez, /readyz, /health and /metrics |
| Errors | RFC 9457 Problem Details for unmatched routes, 405, oversized bodies, timeouts, overload and panics |
| Limits | 2 MiB bodies, 100 headers / 64 KiB request head, 10 s head read, 30 s to response headers, 10 000 connections |
| Telemetry | JSON logs with request and trace ids, one access event per request, Prometheus metrics |
| Shutdown | On SIGTERM/SIGINT readiness reports draining, accepting stops, in-flight requests and streams get 30 s, then telemetry flushes |
| Off until you opt in | CORS, compression, public OpenAPI, the OpenAPI UI, OTLP export, TLS and gateway trust |
One Request Story, Gateway to Service
Alloy works on its own. Behind Ferrum Edge it links each service span to the gateway span it belongs to, and records whether the gateway's identity was cryptographically verified.
SPIFFE identity
from both
Three trust modes
standalone, gateway_preferred and gateway_required. In required mode a direct request that bypasses the gateway gets 403 gateway-required.
Consumer identity handoff
Handlers can take Option<GatewayContext> for Edge's authenticated consumer, accepted only from a verified gateway identity.
Tested against a real gateway
CI runs Edge v0.9.10 and v0.9.9, Alloy and a Collector together: the documented end-to-end checks covering retries, connection reuse, concurrent HTTP/2 streams and cancellation.
Where did the time go? And what does the evidence not prove?
Diagnosis is deterministic. It explains only the supplied evidence, from OTLP exports, report files or one request's live report fetched from a running service, and it never treats input as authenticated.
- Every finding carries a confidence, a scope and its evidence
- Alternative explanations and what the finding does not prove
- Missing evidence and the next checks to run
- Live retrieval is tenant-scoped; every refusal is the same
404
Ferrum Alloy diagnosis report schema: ferrum.diagnostic_report 1.0 trace: 5b8efff798038103d269b633813fc60c observations: 2 Findings (1): 1. [warning] Large unattributed interval between gateway and service confidence: likely scope: gateway_to_upstream rule: alloy.r003 v1 The gateway measured 900.0 ms from backend dispatch to response headers; the service time-to-headers was 120.0 ms. 780.0 ms (87%) is unattributed: neither producer measured it. Evidence: - gateway_telemetry gateway.latency.backend_ttfb_ms = 900.0 ms - service_telemetry alloy.server.time_to_headers = 120.0 ms Does not prove: - network latency - that the service was idle during the interval Next checks: - gateway retry logs for this request
unattributed-interval contract fixture.The ferrum-alloy Command
| Command | Purpose |
|---|---|
ferrum-alloy new | Creates a starter project that compiles and passes its own tests, with optional openapi, postgres, jwt, http-client, otel, edge and tls |
ferrum-alloy check | Validates configuration without starting the service |
ferrum-alloy openapi export | Exports the OpenAPI spec and detects drift |
ferrum-alloy edge export | Exports Ferrum Edge file-mode YAML (checked in CI with ferrum-edge validate) or a GitForgeOps tree. Nothing is applied to a gateway. |
ferrum-alloy diagnose | Explains evidence deterministically, from files or from one request's live report |
Crates and Cargo Features
Take the whole toolkit or just the part you need. Every battery is an opt-in Cargo feature.
ferrum-alloy commandQuick Start
Alloy is not on crates.io. Depend on it by git revision and pin a commit for reproducible builds.
-
Install the CLI and generate a service
bashcargo install --git https://github.com/ferrum-edge/ferrum-alloy --rev 836b61cfb7e58d026cc659fb5152b59949eba327 ferrum-alloy-cli ferrum-alloy new orders-api --with openapi --alloy-rev 836b61cfb7e58d026cc659fb5152b59949eba327 cd orders-api cargo test cargo runThe project contains ordinary Axum handlers,
alloy.toml, a service manifest for gateway configuration, handler tests and a GitHub Actions workflow. -
Or add Alloy to an existing project
toml[dependencies] ferrum-alloy = { git = "https://github.com/ferrum-edge/ferrum-alloy", rev = "836b61cfb7e58d026cc659fb5152b59949eba327" } -
Put it behind Ferrum Edge
bashferrum-alloy edge export --manifest ferrum-service.toml --output edge.yaml ferrum-edge validate -m file -c edge.yamlBuild with the
tlsandedgefeatures and trust the gateway's SPIFFE identity. The getting started guide walks through the certificates and theexamples/edge-observabilitystack.
Works With the Rest of the Stack
Licensing
Ferrum Alloy is licensed under PolyForm Noncommercial 1.0.0; commercial licenses are available. "Ferrum Alloy" and the crate and command names are working names. The bundled Swagger UI assets keep their own Apache-2.0 terms.