AWS S3-compatible object storage in Rust. Single binary, < 20 MB Docker image, no runtime dependencies.
ferroxd ships everything in one process — gateway, metadata store, disk backend, TLS, Prometheus metrics, mTLS admin plane — and speaks the S3 wire protocol well enough that the AWS CLI, Boto3, rclone, the JS / Go SDKs, and pre-signed URLs all work without provider-specific tweaks.
docker run --rm -p 9000:9000 -v ferrox-data:/data \
ghcr.io/ferrox-rs/ferrox:latest \
--data-dir /data --bind 0.0.0.0:9000
# Anywhere AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY are wired:
aws --endpoint-url http://localhost:9000 s3 mb s3://photos
echo "hello, ferrox" | aws --endpoint-url http://localhost:9000 s3 cp - s3://photos/hello.txt5-minute end-to-end walkthrough: docs/getting-started/quickstart.md.
| Ferrox | MinIO | seaweedfs | |
|---|---|---|---|
| Language | Rust | Go | Go |
| Static binary | yes (~12 MB) | yes (~80 MB) | yes |
Docker FROM scratch |
yes | no | no |
| TLS backend | rustls + ring (no OpenSSL) | TLS via Go std | OpenSSL |
| SigV4 + SigV4A | yes | yes | partial |
unsafe in production paths |
none (#![forbid(unsafe_code)]) |
n/a | n/a |
| AGPL or vendor lock-in | no, Apache-2.0 | AGPL | Apache-2.0 |
Goals (in order):
- Drop-in compatibility. If a real AWS SDK breaks against Ferrox, that's a release-blocking bug.
- Strong defaults. SigV4 every request, AES-256-GCM SSE, rustls-only TLS, constant-time compare.
- One binary, zero ceremony. No JVM, no C++ toolchain (default), no init containers, no external metadata DB.
- Open governance. Apache-2.0, public roadmap, every decision in an ADR.
| Surface | Notes |
|---|---|
| ListBuckets / CreateBucket / HeadBucket / DeleteBucket | DNS-compatible name validation |
| PutObject / GetObject (Range) / HeadObject / DeleteObject | Atomic writes, SHA-256 sidecar integrity |
| CopyObject (server-side, including SSE propagation) | via x-amz-copy-source |
| DeleteObjects (batch, ≤ 1000 keys) | <DeleteResult> XML |
| Multipart Upload (Initiate, UploadPart, Complete, Abort, ListParts, ListMultipartUploads) | Background janitor evicts orphans after 24h |
| Bucket versioning | ?versioning PUT/GET |
| Pre-signed URLs (PUT + GET) | UNSIGNED-PAYLOAD honoured; %2F → %252F collisions handled |
| SSE-S3 (AES-256-GCM, KEK + per-object DEK) | configured via --sse-master-key |
| TLS 1.3 / TLS 1.2 (rustls) | dual HTTP+HTTPS listeners, ALPN h2 + http/1.1 |
Docker (FROM scratch, musl) |
< 20 MB image |
| Boto3 + rclone integration test suites | 13 + 10 cases, all passing |
| Surface | Notes |
|---|---|
Object & bucket Tagging (?tagging) |
10 / 128 / 256 limits |
CORS configuration (?cors) + live OPTIONS preflight |
per-origin matching |
| SSE-C (caller-supplied key) | raw key never logged or stored; HMAC-SHA256 fingerprint persisted |
Default bucket encryption policy (?encryption) |
enforced PutObject rejects unencrypted PUTs |
Prometheus /metrics |
requests, latency histogram, bytes-in/out, gauges |
| Health endpoints | /health/live, /health/ready (concurrent meta + disk probes), /health/version |
| Per-access-key rate limiting | governor token-bucket, 503 SlowDown |
| Helm chart | PVC, security context, Prometheus annotations, ingress + TLS |
| cargo-fuzz suite | 3 targets (SigV4 parser, XML, key validator) |
| mdBook docs site (auto-deployed to GitHub Pages) | quickstart, API ref, ADRs |
| Criterion microbenches + wrk macro-bench | regression check in CI |
| Surface | Notes |
|---|---|
| SigV4A (multi-region ECDSA-P256) | parsing + verification scaffold |
| RocksDB metadata backend | opt-in via --features rocksdb |
| mTLS admin API (port 9444) | access-key CRUD, rate-limit overrides, stats |
| Bucket notifications | webhook + SNS-style delivery (tokio::spawn, non-blocking) |
| Terraform modules (AWS, Helm) | with single-node example |
| GitHub Actions release pipeline | musl + darwin binaries, multi-arch GHCR image, signed checksums |
| Erasure-coding backend interface | feature-gated stub; v2 ships RS(4+2) |
- Distributed mode with Reed-Solomon erasure coding (ADR-003).
- Lifecycle policies (
?lifecycle). - Bucket policies + multi-tenant IAM (replacing the v1 single-key identity).
- Streaming SigV4 (
STREAMING-AWS4-HMAC-SHA256-PAYLOAD). - WORM / Object Lock (
?object-lock). - S3 Select / parquet pushdown.
Every roadmap item lives as a tracking issue with a roadmap label.
ferrox/
├─ crates/
│ ├─ ferrox-error/ — thiserror error types + AWS error code mapping
│ ├─ ferrox-crypto/ — SSE-S3 (KEK/DEK), SSE-C (zeroizing CustomerKey)
│ ├─ ferrox-iam/ — identity placeholder (IAM lands post-1.0)
│ ├─ ferrox-meta/ — MetaStore trait + SledMeta + (optional) RocksMeta
│ ├─ ferrox-storage/ — StorageBackend trait + DiskBackend + (stub) ErasureBackend
│ ├─ ferrox-s3-api/ — S3 XML serializers + parsers, name validators
│ ├─ ferrox-gateway/ — axum router, SigV4, all handlers, metrics, ratelimit, admin, notify
│ └─ ferrox-cli/ — ferroxd binary entrypoint
├─ helm/ferrox/ — Helm chart
├─ terraform/ — modules/ferrox-aws, modules/ferrox-k8s, examples
├─ docs/ — mdBook source (deployed to GitHub Pages)
├─ docs/adr/ — Architecture Decision Records
├─ fuzz/ — cargo-fuzz targets
├─ scripts/bench/ — wrk macro benchmarks
└─ tests/integration/ — Boto3 + rclone interop suites
git clone https://github.com/ferrox-rs/ferrox.git
cd ferrox
cargo run --bin ferroxd -- \
--data-dir ./data \
--bind 0.0.0.0:9000 \
--access-key minioadmin \
--secret-key minioadmin
# In another terminal
curl http://localhost:9000/health/live
# {"status":"ok"}Full configuration: docs/getting-started/configuration.md.
PRs welcome — see CONTRIBUTING.md for the full guide. TL;DR:
- Pick or open an issue. Issues tagged
good first issueare bounded and have a clear acceptance criterion. - Branch off
main. Keep PRs focused — one bug fix or one feature each. - Run
cargo fmt,cargo clippy --workspace --all-targets -- -D warnings, andcargo test --workspacebefore pushing. - AWS-compat changes must be verified against real AWS S3 behaviour (Boto3 / AWS CLI). The PR template asks for the verification command.
- Update
CHANGELOG.mdunder## [Unreleased].
Discussions, design questions, and weekly sync notes live in GitHub Discussions. Real-time chat: #ferrox on the Rust Discord.
Found a vulnerability? Please use GitHub Security Advisories (Security tab) — full policy in SECURITY.md.
Hardening notes:
#![forbid(unsafe_code)]in every crate.- SigV4 verification uses constant-time HMAC compare.
- TLS is rustls + ring only — no OpenSSL.
- SSE-C raw keys never persist or appear in logs.
- All key material wiped on drop via
zeroize. cargo auditruns in CI on every PR.
Ferrox is licensed under the Apache License, Version 2.0. See LICENSE.
By contributing, you agree that your contributions will be licensed under the same terms.