Quick start
All examples on this page assume Rustybin answers on http://localhost:8080.
Docker
There is no published image: build it from the repository (the build compiles a release binary, which takes a few minutes the first time).
git clone https://github.com/oliwynn/rustybin && cd rustybin
docker build -t rustybin .
docker run -d --name rustybin -p 8080:80 -p 8443:443 -p 50051:50051 rustybin
The container listens on 80 (HTTP), 443 (HTTPS) and 50051 (gRPC) and runs as an
unprivileged user (uid 10001). On first start it generates a demo PKI in /app/certs
(a CA, the HTTPS server certificate and a client certificate for mTLS demos); mount
a volume on /app/certs to keep it across restarts (a bind-mounted host directory
must be writable by uid 10001). The image also runs with --read-only when
/app/certs is a volume, see Containers.
Settings are environment variables, see Configuration:
docker run -d -p 8080:80 -e RUSTYBIN_INSTANCE_ID=demo-1 -e RUSTYBIN_ADMIN_TOKEN=change-me rustybin
Docker Compose
docker-compose.yml builds the image, publishes ports 80, 443 and 50051 and keeps
the demo PKI in the named volume rustybin-certs so it survives restarts (download
the CA with curl -o ca.crt http://localhost/auth/mtls/get-ca-cert):
docker compose up -d
curl http://localhost/health
The file also contains a commented-out block for two more instances
(rustybin-02, rustybin-03) with their own RUSTYBIN_INSTANCE_ID, for load
balancing demos (see Identity and load balancing).
Cargo
Rust 1.88 or newer. The default ports (80 and 443) need root, so pick high ports:
RUSTYBIN_HTTP_PORT=8080 RUSTYBIN_HTTPS_PORT=8443 cargo run --release
cargo run writes the demo PKI to certs/ in the working directory (the default
RUSTYBIN_TLS_CERT is certs/server.crt); the files are git-ignored. Other flags:
cargo run -- --version # print the version
cargo run -- --print-endpoints-markdown # print the endpoint tables (from the route catalogue)
cargo run -- --config rustybin.yaml # run with OpenAPI mocks declared in a file
cargo run -- mock --help # the OpenAPI mock CLI
See OpenAPI mocks for rustybin.yaml, mounting specs into the
container and the rustybin mock commands.
Fly.io
fly.toml deploys the Dockerfile with the web surface behind Fly's edge TLS and the
gRPC port as raw TCP passthrough:
fly launch --copy-config --no-deploy # once: pick an app name and region
fly deploy
Things to know about the provided configuration:
RUSTYBIN_TRUST_FORWARD=true: Fly's proxy setsFly-Client-IPandX-Forwarded-*, so/ip,/echoand the OIDC issuer report the real client andhttpsscheme.- The HTTP health check targets
GET /_rustybin/ready(always 200 while serving), not/health, which is a demo toggle that can return 503. - The image runs as an unprivileged user, so the configuration moves the listeners
to ports 8080 (HTTP, Fly's
internal_port) and 8443 (HTTPS). - To serve gRPC through Fly's TLS edge on 443 instead of a raw port, set
RUSTYBIN_GRPC_ON_HTTP=trueand give the 443 service an HTTP/2 backend (see gRPC). - Because Fly terminates TLS for the web surface, Rustybin's own HTTPS listener and
mTLS on it are not reachable; use the
RUSTYBIN_MTLS_IN_HEADERmode for mTLS demos behind a gateway instead (see Authentication). - gRPC on port 50051 is plaintext h2c:
grpcurl -plaintext <app>.fly.dev:50051 list. - For a shared, internet-facing instance set
RUSTYBIN_PUBLIC_MODE=true(see Sessions, public mode and the admin token).
First requests
# Echo: see exactly what arrived (method, path, query, headers, body)
curl -s 'http://localhost:8080/anything/hello?demo=1' -H 'X-Demo: rustybin'
# Mock LLM, OpenAI wire format
curl -s http://localhost:8080/ai/openai/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"hello"}]}'
# An OpenAPI mock: the built-in Petstore sample (upload your own spec in /ui/#/mocks)
curl -s http://localhost:8080/mock/petstore/v1/pets/42
# MCP: start a session (2025-06-18 handshake)
curl -si http://localhost:8080/mcp -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# A2A: the default agent card
curl -s http://localhost:8080/.well-known/agent-card.json
# Chaos on any route
curl -si http://localhost:8080/echo -H 'X-Rustybin-Fail: 503'
# An access token from the built-in identity provider
curl -s http://localhost:8080/oauth/token \
-d grant_type=client_credentials -d client_id=rustybin -d client_secret=secret
The same requests as a tested Hurl file
GET {{base_url}}/anything/hello?demo=1
X-Demo: rustybin
HTTP 200
[Asserts]
jsonpath "$.path" == "/anything/hello"
jsonpath "$.headers['x-demo'][0]" == "rustybin"
POST {{base_url}}/ai/openai/v1/chat/completions
Content-Type: application/json
{"model": "gpt-4o", "messages": [{"role": "user", "content": "hello"}]}
HTTP 200
[Asserts]
jsonpath "$.choices[0].message.content" startsWith "Hello! I'm Rustybin"
GET {{base_url}}/mock/petstore/v1/pets/42
HTTP 200
[Asserts]
header "X-Rustybin-Mock-Operation" == "getPetById"
jsonpath "$.id" == 42
POST {{base_url}}/mcp
Accept: application/json, text/event-stream
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1"}}}
HTTP 200
[Asserts]
header "Mcp-Session-Id" exists
jsonpath "$.result.serverInfo.name" == "rustybin-mcp"
GET {{base_url}}/.well-known/agent-card.json
HTTP 200
[Asserts]
jsonpath "$.name" == "Echo Agent"
GET {{base_url}}/echo
X-Rustybin-Fail: 503
HTTP 503
POST {{base_url}}/oauth/token
[Form]
grant_type: client_credentials
client_id: rustybin
client_secret: secret
HTTP 200
[Asserts]
jsonpath "$.token_type" == "Bearer"
Then open http://localhost:8080/ for the endpoint list with runnable examples,
/docs for the interactive API reference and /ui for the
web console.