MAP Docs

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 = true

Deploy:

flyctl deploy --config fly.mim.toml

Dockerfile

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:

ServiceDockerfileFly configDefault port
MIMDockerfile.mimfly.mim.toml8080
Engine (orchestrator)Dockerfile.enginefly.engine.toml8080
Gateway (HTTP ingress)Dockerfile.gatewayfly.gateway.toml443
MCP serverDockerfile.mcpfly.mcp.toml8601
Admin consoleDockerfile.adminfly.admin.toml3000

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:

VariableDefaultPurpose
RUST_LOGinfoTracing level filter
APP_BIND_ADDR0.0.0.0:8080HTTP listen
MAP_AGENT_DIDrequiredEngine's own DID
MAP_API_URLUpstream MAP URL (when this is a relay)
MAP_TENANT_IDrequiredTenant scope for the engine's own actions
MAP_WASMTIME_VERSION37.0.0Pinned Wasmtime version
MAP_AUDIT_BUFFER_DIR/var/lib/map/auditDisk buffer for audit overflow
MAP_AUDIT_SINKproductionproduction | kafka | memory
MAP_RATE_LIMIT_BACKENDredisredis | memory
MAP_REDIS_URLrequired for rate-limitredis://host:6379/0
MAP_DB_URLrequiredPostgres connection string for MAX chain
MAP_PROMETHEUS_PORT9091Metrics scrape port
MAP_OTEL_ENDPOINTOTLP collector endpoint for tracing
MAP_PLUGIN_DIR/var/lib/map/pluginsPlugin scan directory
MAP_PLUGIN_REQUIRE_SIGNATUREStrueReject 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.sh

Indexes 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

SignalTargetFormat
Logsstdout/stderrstructured tracing (JSON)
Metrics:9091/metricsPrometheus exposition
TracesOTLP exporterOpenTelemetry
AuditPostgreSQL (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:

  1. Scale the gateway horizontally — it's stateless behind a load balancer
  2. Scale the engine horizontally — Redis-backed rate limit makes this safe
  3. Scale heavy agents independently — MARC, MAGI, MACE typically warrant their own clusters with GPUs
  4. 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_VERSION already 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:

  1. Stop accepting new requests (gateway returns 503)
  2. Drain in-flight requests (30s grace period)
  3. Flush audit pipeline (synchronous)
  4. Disconnect from database / redis
  5. Exit

Plugin unload happens in step 4 — Wasmtime instances are dropped cleanly.

See also

On this page