Deployment
Fly.io reference deployment, Docker multi-stage build, env vars, secrets, observability stack.
MAP ships with a production Fly.io configuration as the reference deployment. Docker builds via a multi-stage Dockerfile.mim. The engine binary is statically linked and runs as nonroot in distroless/cc-debian12.
Reference: Fly.io
The repo's fly.mim.toml deploys the MIM service:
app = "map-mim"
primary_region = "iad"
[build]
dockerfile = "Dockerfile.mim"
[env]
RUST_LOG = "info,hyper=warn"
APP_BIND_ADDR = "0.0.0.0:8080"
MIM_AGENT_DID = "did:l1fe:router:mim-prod"
MIM_DEFAULT_TRANSPORT = "http"
MIM_QOS_DEFAULT_LEVEL = "AtLeastOnce"
MIM_ENABLE_ENCRYPTION = "true"
MIM_ENABLE_SIGNATURE = "true"
MIM_MAX_MESSAGE_SIZE_BYTES = "5242880"
MAP_WASMTIME_VERSION = "37.0.0"
[metrics]
port = 9091
path = "/metrics"
[[vm]]
cpus = 2
memory_mb = 2048
[http_service]
internal_port = 8080
force_https = trueDeploy:
flyctl deploy --config fly.mim.tomlDockerfile
Dockerfile.mim is a three-stage build:
# 1. Cargo chef stage — precompute dependency graph
FROM rust:1.78 AS chef
RUN cargo install cargo-chef
# 2. Builder stage — compile with feature flags for prod
FROM chef AS builder
COPY . .
RUN cargo chef cook --release --recipe-path recipe.json
RUN cargo build --release --features native-adapter,production-storage \
-p mim-service
# 3. Runtime stage — distroless / nonroot
FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=builder /target/release/mim-service /mim-service
USER nonroot
EXPOSE 8080 9091
ENTRYPOINT ["/mim-service"]Final image is ~25 MB (distroless + statically-linked Rust binary). No shell. No package manager. No interactive surface.
Per-service deployments
The repo ships per-service Dockerfile + Fly config patterns. Each MAP service deploys independently:
| Service | Dockerfile | Fly config | Default port |
|---|---|---|---|
| MIM | Dockerfile.mim | fly.mim.toml | 8080 |
| Engine (orchestrator) | Dockerfile.engine | fly.engine.toml | 8080 |
| Gateway (HTTP ingress) | Dockerfile.gateway | fly.gateway.toml | 443 |
| MCP server | Dockerfile.mcp | fly.mcp.toml | 8601 |
| Admin console | Dockerfile.admin | fly.admin.toml | 3000 |
You can deploy all of MAP as a monolith (mim-service embeds the engine) or as separate services. The reference architecture is separate services with the engine in front.
Environment variables
The engine reads:
| Variable | Default | Purpose |
|---|---|---|
RUST_LOG | info | Tracing level filter |
APP_BIND_ADDR | 0.0.0.0:8080 | HTTP listen |
MAP_AGENT_DID | required | Engine's own DID |
MAP_API_URL | — | Upstream MAP URL (when this is a relay) |
MAP_TENANT_ID | required | Tenant scope for the engine's own actions |
MAP_WASMTIME_VERSION | 37.0.0 | Pinned Wasmtime version |
MAP_AUDIT_BUFFER_DIR | /var/lib/map/audit | Disk buffer for audit overflow |
MAP_AUDIT_SINK | production | production | kafka | memory |
MAP_RATE_LIMIT_BACKEND | redis | redis | memory |
MAP_REDIS_URL | required for rate-limit | redis://host:6379/0 |
MAP_DB_URL | required | Postgres connection string for MAX chain |
MAP_PROMETHEUS_PORT | 9091 | Metrics scrape port |
MAP_OTEL_ENDPOINT | — | OTLP collector endpoint for tracing |
MAP_PLUGIN_DIR | /var/lib/map/plugins | Plugin scan directory |
MAP_PLUGIN_REQUIRE_SIGNATURES | true | Reject unsigned plugins |
Plus per-protocol vars (e.g., MIM_QOS_DEFAULT_LEVEL). See the protocol's spec page.
Secrets
Set via Fly's secret store, never in fly.toml:
flyctl secrets set \
MAP_AGENT_DID="did:oas:l1fe:agent:0x..." \
MAP_DB_URL="postgres://map:$(openssl rand -hex 32)@db.fly.dev/map" \
MAP_REDIS_URL="redis://default:$(...)@redis.fly.dev:6379/0"Rotate annually. The audit signing key has a separate rotation procedure — see Security.
Database
The MAX chain lives in PostgreSQL with the pgcrypto extension. Schema in migrations/:
psql $MAP_DB_URL < migrations/0001_init.sql
psql $MAP_DB_URL < migrations/0002_audit_chain.sql
# ...Or use the bundled runner:
./run_migrations.shIndexes are optimized for correlation_id lookup and time-range scans. For tenants > 1B records, partition audit_chain by month.
Redis
Used for:
- Rate-limit token buckets (per tenant + protocol)
- Capability set cache (TTL 60s)
- Module-cache warmth signal across replicas
A single Redis instance suffices for hundreds of millions of requests/day. Cluster Redis for higher scale.
TLS
The gateway terminates TLS. Behind Fly.io, the platform issues certs automatically. For sovereign deployments, bring your own certs via:
[gateway.tls]
cert_file = "/etc/map/tls/fullchain.pem"
key_file = "/etc/map/tls/privkey.pem"Observability stack
| Signal | Target | Format |
|---|---|---|
| Logs | stdout/stderr | structured tracing (JSON) |
| Metrics | :9091/metrics | Prometheus exposition |
| Traces | OTLP exporter | OpenTelemetry |
| Audit | PostgreSQL (via MAX) | hash-chained rows |
The default observability stack we run alongside MAP:
- Loki for log aggregation
- Prometheus for metrics scrape
- Tempo for traces
- Grafana for dashboards (pre-built dashboards in
ops/grafana/)
See Observability for what each protocol emits.
Scaling
A single-VM deployment handles ~3-5k req/s at p50 < 5ms. To scale beyond that:
- Scale the gateway horizontally — it's stateless behind a load balancer
- Scale the engine horizontally — Redis-backed rate limit makes this safe
- Scale heavy agents independently —
MARC,MAGI,MACEtypically warrant their own clusters with GPUs - Partition the audit chain by tenant for write throughput
The audit chain itself is the eventual bottleneck for write-heavy workloads. The hash-chained design serializes writes per-tenant; cross-tenant throughput scales linearly.
Sovereign deployment
For air-gapped or on-prem: the same Dockerfiles plus:
- Disable
MAP_OTEL_ENDPOINT(no outbound telemetry) - Configure the OAS resolver to use a local registry mirror
- Use a self-hosted CA for TLS (engines accept the chain via
MAP_TLS_CA) - Pin Wasmtime version (
MAP_WASMTIME_VERSIONalready set)
For full sovereign deployment guidance — including key custody, audit-chain off-site storage, multi-region replication — see the Sovereign tier onboarding at sovereign@l1fe.ai.
Health checks
The engine exposes:
GET /health → 200 OK if accepting requests
GET /ready → 200 OK if all upstreams healthy
GET /metrics → Prometheus exposition (port 9091 by default)/ready returns 503 during startup, during the shutdown grace period (30s default), and when any of these are unhealthy: database, redis, plugin loader.
Graceful shutdown
The engine handles SIGTERM:
- Stop accepting new requests (gateway returns 503)
- Drain in-flight requests (30s grace period)
- Flush audit pipeline (synchronous)
- Disconnect from database / redis
- Exit
Plugin unload happens in step 4 — Wasmtime instances are dropped cleanly.