Services and their jobs¶
ExaMLOps runs as 30 cooperating services, stores and external dependencies. Twenty of them are
Docker Compose services: make stack-up starts the core ten, make monitoring-up six
observability services, make jupyter-up JupyterHub and make seanerbus-up the bus bridge; the
backup sidecar and vLLM are opt-in profiles (docker compose --profile backup|vllm up). The rest
are processes, stores and external systems they work with. This page lists
each one: what it does, where it listens, who it talks to and how to operate it. The
system map shows how they connect; the tours show them at work.
Ports are the host ports of the local development stack.
| Service | Plane | Port | Its job |
|---|---|---|---|
| Ray Serve MultiModelServer | Serving | 18001, 18265, 18080 | Serves every model alias; hot-reloads when an alias moves |
| Inference pipeline (InferencePipelineIngress -> FeatureTransformer -> ModelRouter) | Serving | 18001 | Validates, batches and routes each request by traffic split |
| vLLM OpenAI-compatible server | Serving | 18011 | OpenAI-compatible LLM server on a GPU (optional profile) |
| Control plane API | Control and training | 18002 | Retrain API, approval queue, ModelZoo sync, event relay |
| Prefect server (orchestrator) | Control and training | 14200 | Stores deployments, schedules and flow-run state |
| Prefect deployment runner + training_flow | Control and training | — | Serves each model's deployment and runs the training flow |
| HPC scheduler adapters (mock / Slurm / Flux) | Control and training | — | Submits training to mock, Slurm or Flux |
| MLflow tracking server + model registry | Registry and storage | 15000 | Tracks runs; model registry with lifecycle aliases |
| MinIO object store | Registry and storage | 19000, 19001 | Object storage for artifacts, projects and datasets |
| MinIO bucket init (one-shot) | Registry and storage | — | Creates the buckets on first start, then exits |
| PostgreSQL (MLflow + Prefect metadata) | Registry and storage | — | Databases for MLflow, Prefect and the dashboard |
| Shared platform datastore (platform.db) | Registry and storage | — | Shared platform state: drift, traffic, audit, jobs, projects |
| Backup sidecar | Registry and storage | — | Scheduled backup of databases and buckets (optional profile) |
| SeanerBUS bridge | Integration | 18003 | Connects the site message bus to serving and retraining |
| SeanerBUS message bus (external) | Integration | 5398 | The site's message bus (external system) |
| Event outbox relay (NovaFabric backbone) | Integration | — | Publishes outbox events at least once |
| Dashboard (FastAPI BFF + React SPA) | People and agents | 18099 | Web consoles over the same code paths as the CLI |
| Scoped Docker socket proxy | People and agents | — | Gives the dashboard the containers API only — no images, exec, volumes or networks |
| Skipper agent server (web UI + OpenAI-compatible bridge) | People and agents | 18004 | Plain-English operations agent with confirmed writes |
| Skipper-watch monitoring daemon | People and agents | — | LLM-free watcher for drift and budget breaches |
| ExaMLOps MCP server | People and agents | 8765 | Platform tools for other agents; read-only unless allowed |
| JupyterHub (+ spawned JupyterLab) | People and agents | 18888 | Per-user notebooks and project workbenches (optional profile) |
| Ollama LLM backend (external) | People and agents | 11436 | Local LLM and embeddings for the agent (external) |
| Prometheus | Observability | 19090 | Scrapes metrics and evaluates alert rules |
| Alertmanager | Observability | 19093 | Groups and routes alerts to paging and chat |
| Grafana | Observability | 13000 | Dashboards over metrics, logs and traces |
| Loki | Observability | 13100 | Stores container logs for 7 days |
| Promtail | Observability | — | Ships every container's logs to Loki |
| Grafana Tempo | Observability | 13200 | Stores traces for 48 hours when tracing is on |
| OpenTelemetry Collector (production config, not in compose) | Observability | — | Production trace pipeline (configuration only) |
Libraries, not services
The model gateway and the inference gateway are Python libraries used inside other processes, and the event relay runs as a thread in the control plane. The Helm chart deploys the control plane, dashboard and agent; Postgres and object storage are expected to be provided by the cluster.
Serving¶
Answer predictions.
Ray Serve MultiModelServer¶
Port: 18001, 18265, 18080
Built on: Ray Serve + FastAPI ingress, python:3.12-slim (serving/ray_serving/Dockerfile); 18001 API, 18265 Ray dashboard, 18080 Prometheus metrics
What it does
- Pre-loads a hot set of (model, alias) for Production/Canary/Staging from MLflow at start
- Serves POST /predict/{model} with optional alias or raw version (raw versions in an LRU of 8)
- Polls MLflow every 60s and hot-reloads any model whose alias version moved; also POST /reload and /reload/{model}
- Runs predictions under a 30s hard timeout on a 4-worker pool and recycles the pool if predicts hang
- Mirrors traffic to a shadow alias off the request thread (bounded 16 in-flight, drops when full) and writes shadow_results
- Exports request/latency/hot-set/reload metrics; /ready for liveness, /health returns 503 when hot set is empty
Talks to: MLflow tracking server + model registry (HTTP); MinIO object store (S3); Shared platform datastore (platform.db) (SQL); Grafana Tempo (OTLP gRPC)
Keeps: in-memory hot set + version LRU
Operate: make stack-up (service: ray-serving); exa serve check | exa serve reload | exa serve infer-check
Guide: ray serve
Inference pipeline (InferencePipelineIngress -> FeatureTransformer -> ModelRouter)¶
Port: 18001
Built on: Ray Serve deployment graph on route prefix /infer-pipeline (same Ray cluster), num_cpus=0 per actor
What it does
- Ingress validates payloads on POST /infer-pipeline/infer (422 on schema errors)
- FeatureTransformer micro-batches requests up to 32 or 50 ms and checks 384-dim embedding + num_nodes
- ModelRouter applies weighted canary/production traffic splits read from traffic_rules (30s TTL cache)
- Forwards to MultiModelServer POST /predict/{model} with 2 retries on transport errors
- Exposes GET/POST /traffic-rules and persists splits to platform-db
Talks to: Ray Serve MultiModelServer (HTTP); Shared platform datastore (platform.db) (SQL)
Keeps: traffic_rules (platform-db)
Operate: starts with ray-serving; exa serve traffic JPCP --production 90 --canary 10
Guide: interfaces
vLLM OpenAI-compatible server¶
Port: 18011
Built on: vllm/vllm-openai:latest, NVIDIA GPU reservation; compose profile vllm (host port EXAMLOPS_VLLM_HOST_PORT default 18011 -> 8000)
What it does
- Serves an OpenAI-compatible LLM endpoint for EXAMLOPS_VLLM_MODEL
- Uses engine flags rendered by engines.to_vllm_args() (same renderer as the Slurm template and KServe manifest)
- Caches Hugging Face weights in the vllm_cache volume
- Exports TTFT / inter-token latency / KV-cache metrics scraped by Prometheus
Keeps: volume vllm_cache
Operate: exa serve llm start <model> --launcher compose (or docker compose --profile vllm up -d vllm); HPC: --launcher slurm|flux
Guide: llm serving engines
Control and training¶
Decide when to retrain and run the training flow.
Control plane API¶
Port: 18002
Built on: FastAPI + uvicorn under opentelemetry-instrument (platform/services/control_plane/Dockerfile); also a Helm template
What it does
- Accepts POST /retrain (bearer token, 20/min rate limit, X-Idempotency-Key) and creates a Prefect flow run behind a circuit breaker (opens after 5 failures, 30s reset)
- Runs the sysadmin approval gate: CI POST /api/changes -> pending_approvals -> POST /approve|/reject/{model} (approve starts retrain); expires stale approvals after 72h
- Receives ModelZoo GitLab/GitHub webhooks and polls the upstream repo every 300s under a leader lease, marking models stale
- Serves model meta/README/images, /status (pings Prefect, MLflow, Ray, dashboard) and /metrics for Prometheus
- Relays the transactional event outbox every 1s on a background thread
- Enforces admission queue caps (4 global / 2 per tenant by default)
Talks to: Prefect server (orchestrator) (HTTP (Prefect API)); MLflow tracking server + model registry (HTTP); Ray Serve MultiModelServer (HTTP); Dashboard (FastAPI BFF + React SPA) (HTTP); Shared platform datastore (platform.db) (SQL); Event outbox relay (NovaFabric backbone) (in-process); Grafana Tempo (OTLP gRPC)
Keeps: pending_approvals, modelzoo_events, model_freshness, control_plane_commands, admission_queue and event_outbox — in the shared platform.db in the dev stack (PLATFORM_DB=/state/platform.db); a standalone deployment uses $CONTROL_PLANE_DB, default /data/approvals.db
Operate: make control-plane-up ; exa retrain JPCP --dataset PM100Dataset ; exa approvals list|approve|reject
Guide: control plane
Prefect server (orchestrator)¶
Port: 14200
Built on: prefecthq/prefect:3.6.27-python3.11 (Dockerfile.orchestrator), prefect server start
What it does
- Stores deployments, schedules (e.g. nightly cron 0 2 * * *) and flow-run state
- Accepts flow-run creation from the control plane (POST /retrain, approvals, ModelZoo auto-retrain)
- Serves the Prefect UI and REST API (/api) on 14200
- Persists metadata in Postgres DB prefect instead of ephemeral SQLite
Talks to: PostgreSQL (MLflow + Prefect metadata) (PostgreSQL (asyncpg))
Keeps: Postgres DB prefect
Operate: make stack-up (service: orchestrator); UI http://localhost:14200
Guide: prefect
Prefect deployment runner + training_flow¶
Port: internal only
Built on: Python process: pipelines/deploy.py calling flow.serve()/prefect.serve() (not a compose service)
What it does
- Registers the training deployments and serves them in-process: one wrapper deployment for all models by default, or one per enabled model YAML (cron, work pool, concurrency limit) with
--registry - Runs training_flow: data_extraction -> data_contract_gate -> slurm_submit -> slurm_wait -> result_fetch -> evaluate -> log_mlflow -> promote
- Submits training to the selected scheduler adapter (EXAMLOPS_HPC_SCHEDULER=mock|slurm|flux) and records hpc_jobs rows
- Walks lifecycle rules (Staging/Canary/Production thresholds), sets MLflow aliases and moves the previous Production to Archived
- POSTs /reload/{model} to Ray Serve right after promotion (webhook path)
- Routes runs to per-project MLflow experiments (project/
)
Talks to: Prefect server (orchestrator) (HTTP (Prefect API)); MLflow tracking server + model registry (HTTP); MinIO object store (S3); HPC scheduler adapters (mock / Slurm / Flux) (in-process Python); Ray Serve MultiModelServer (HTTP); Shared platform datastore (platform.db) (SQL)
Keeps: Prefect deployments (in prefect-server)
Operate: exa pipeline deploy [--no-schedule] ; one-off: exa pipeline run --model JPCP --dataset PM100Dataset
Guide: hpc training workflow
HPC scheduler adapters (mock / Slurm / Flux)¶
Port: internal only
Built on: Python library platform/infra/slurm-adapter (SchedulerAdapter protocol + LocalExecutor/SSHExecutor via paramiko)
What it does
- Mock: simulates submission locally, trains inline (sklearn) or runs the script as a subprocess
- Slurm: submits with sbatch, polls squeue then sacct, fetches the StdOut log path
- Flux: submits with
flux batch, pollsflux jobs/ eventlog - Caps every scheduler CLI call at 30s, total wait at 24h and 5 consecutive UNKNOWN polls
- Runs commands locally or over SSH (host-key RejectPolicy by default) so a containerised worker can reach a login node
- Discovers nodes/GPUs (SlurmProbe/FluxProbe/NvidiaSmiProbe) for
exa hpc detect/nodes/gpus
Talks to: Prefect deployment runner + training_flow (in-process)
Keeps: mock_hpc_jobs/ working dir; hpc_jobs / hpc_nodes / hpc_clusters tables in platform-db
Operate: EXAMLOPS_HPC_SCHEDULER=mock|slurm|flux ; exa hpc detect | exa hpc jobs | exa hpc preflight
Guide: slurm adapter
Registry and storage¶
Hold models, artifacts, datasets and platform state.
MLflow tracking server + model registry¶
Port: 15000
Built on: ghcr.io/mlflow/mlflow:v3.16.0 + psycopg2 + boto3 (Dockerfile.mlflow)
What it does
- Records training runs, params and metrics logged by the Prefect training flow
- Holds the model registry with lifecycle aliases (Staging / Canary / Production / Archived)
- Proxies artifact upload/download to MinIO (--artifacts-destination s3://mlflow-artifacts/)
- Answers alias lookups for Ray Serve's hot-set loader and 60s alias poller
- Serves the MLflow UI on 15000
Talks to: PostgreSQL (MLflow + Prefect metadata) (PostgreSQL); MinIO object store (S3)
Keeps: experiments/runs/registered models in Postgres DB mlflow; artifacts in bucket mlflow-artifacts
Operate: make stack-up (service: mlflow); UI http://localhost:15000
Guide: mlflow
MinIO object store¶
Port: 19000, 19001
Built on: minio/minio:latest (S3 API :9000 -> 19000, console :9001 -> 19001)
What it does
- Serves the S3 API used by MLflow for model artifacts (s3://mlflow-artifacts/)
- Stores per-project storage under s3://examlops-projects/
/{artifacts,datasets,cache}/ - Stores dashboard model-doc images in the dashboard-model-docs bucket (created by the dashboard at startup)
- Acts as a dataset backend (minio) for training when no dedicated dataset S3 is configured
- Exposes the MinIO Console UI on 19001
Keeps: volume minio_data; bucket mlflow-artifacts; bucket examlops-projects; bucket dashboard-model-docs
Operate: make stack-up (service: minio)
Guide: interfaces
MinIO bucket init (one-shot)¶
Port: internal only
Built on: minio/mc:latest, restart: no
What it does
- Waits for MinIO to be healthy
- Creates bucket mlflow-artifacts if absent (mc mb --ignore-existing)
- Creates the project-storage bucket ($EXAMLOPS_PROJECTS_BUCKET, default examlops-projects)
- Exits 0; MLflow waits for its successful completion
Talks to: MinIO object store (S3 (mc))
Operate: runs automatically on make stack-up
Guide: project anatomy
PostgreSQL (MLflow + Prefect metadata)¶
Port: internal only
Built on: postgres:15 (Dockerfile.postgres), no host port published
What it does
- Hosts the
mlflowdatabase used as the MLflow backend store - Hosts the
prefectdatabase (created by initdb-prefect.sql on first volume init) for Prefect server metadata - Hosts the dashboard's Alembic-managed tables (dashboard_config, dashboard_audit, model_doc_overrides, model_doc_images) via DATABASE_URL on the mlflow DB
- Health-gated via pg_isready every 5s; MLflow, Prefect, dashboard and backup wait for it
- Optional target for platform state when EXAMLOPS_DB_BACKEND=postgres (separate DSN via EXAMLOPS_POSTGRES_DSN)
Keeps: volume postgres_data; DB mlflow; DB prefect
Operate: make stack-up (service: postgres)
Guide: architecture
Shared platform datastore (platform.db)¶
Port: internal only
Built on: SQLite file (PLATFORM_DB, WAL) by default; Postgres via EXAMLOPS_DB_BACKEND=postgres + EXAMLOPS_POSTGRES_DSN through the examlops.storage seam
What it does
- Holds about 130 tables of platform state: audit_events (hash-chained), drift_snapshots/baselines, input_snapshots, traffic_rules, shadow_config/results, hpc_jobs/nodes/clusters, projects, event_outbox, autopilot_runs, virtual_keys, prompt_versions and more
- Shared by CLI, control plane, agent, bridge, dashboard and Ray Serve through a /state bind mount
- Bootstraps its schema once per process
- Coordinates processes: leases/locks, rate windows and idempotency (coord_* tables)
Keeps: platform.db (repo root / $EXAMLOPS_STATE_DIR)
Operate: exa audit | exa drift status | exa data retention-prune --days N ; make test-postgres
Guide: postgres backend
Backup sidecar¶
Port: internal only
Built on: python:3.12-slim + examlops[backup]; entrypoint exa backup schedule --all; compose profile backup
What it does
- Backs up platform.db (which also holds the control plane's state in the dev stack) and a standalone approvals DB when present (SQLite tier)
- Dumps Postgres databases mlflow and prefect
- Mirrors MinIO buckets (MLflow artifacts + project storage)
- Runs every EXAMLOPS_BACKUP_INTERVAL (default 3600s) and prunes to keep=14
- Replicates off-site when EXAMLOPS_BACKUP_S3_URI is set; skips unavailable tiers without crashing
Talks to: PostgreSQL (MLflow + Prefect metadata) (PostgreSQL); MinIO object store (S3); Shared platform datastore (platform.db) (file)
Keeps: volume backups_data
Operate: docker compose --profile backup up -d backup ; exa backup ...
Guide: backup restore
Integration¶
Connect external systems and deliver events.
SeanerBUS bridge¶
Port: 18003
Built on: Python asyncio + pycapnp (Cap'n Proto over TCP) + httpx; Dockerfile.bridge; compose profile seanerbus
What it does
- Registers one req/res inference handler per model seanerbus_uuid (HpcJobV1 -> HpcInferenceResV1) plus retrain (RetrainReqV1) and vector (VectorReqV1) handlers
- Forwards jobs to Ray Serve /infer-pipeline/infer (vector requests go straight to /predict/{model})
- Writes drift snapshots, input-embedding stats (norm/mean/std) and audit events off the event loop
- Tracks per-model rolling error rate (window 50, threshold 0.5, cooldown 300s) and POSTs /retrain to the control plane on drift
- Serves /health, /stats and /metrics on 8003 and reconnects to the bus with exponential backoff (2s to 30s)
Talks to: SeanerBUS message bus (external) (Cap'n Proto/TCP :5398); Inference pipeline (InferencePipelineIngress -> FeatureTransformer -> ModelRouter) (HTTP); Ray Serve MultiModelServer (HTTP); Control plane API (HTTP); Shared platform datastore (platform.db) (SQL)
Operate: make seanerbus-up (container) or make seanerbus-bridge-up (bare metal) ; exa seanerbus status
Guide: seanerbus
SeanerBUS message bus (external)¶
Port: 5398
Built on: External system from the sibling seanerbus repo; reached over docker network seanerbus-net
What it does
- Carries HPC job / inference / retrain / vector messages between site components and ExaMLOps
- Runs a request generator (reqgen) used by make full-up
Talks to: SeanerBUS bridge (Cap'n Proto/TCP)
Operate: cd ../seanerbus && docker compose up -d (external); make full-up
Guide: seanerbus architecture
Event outbox relay (NovaFabric backbone)¶
Port: internal only
Built on: examlops.events.relay_once: thread inside control plane + exa events relay CLI
What it does
- Claims unpublished rows from the event_outbox table in batches (default 100)
- Publishes each via EXAMLOPS_EVENT_PUBLISHER: log (default) or Redis Streams; nats/kafka fail loudly and keep the row
- Marks rows published/failed with stable IDs (outbox:
) for at-least-once delivery - Reports pending / published / poison backlog (
exa events stats)
Talks to: Shared platform datastore (platform.db) (SQL)
Keeps: event_outbox table
Operate: exa events relay [--loop] ; exa events stats ; in-process in control plane (CONTROL_PLANE_EVENT_RELAY_SECONDS=1)
Guide: control plane
People and agents¶
The ways people and agents operate the platform.
Dashboard (FastAPI BFF + React SPA)¶
Port: 18099
Built on: FastAPI + Alembic backend; React 19 + Vite + Tailwind frontend built into the same image (node:24-alpine -> python:3.12-slim)
What it does
- Serves the React SPA and ~58 API routers (MLOps, facility, LLMOps, FinOps, governance, projects, connections, workbenches)
- Aggregates upstream status in a BFF and streams typed realtime events over SSE (GET /api/v1/stream)
- Starts/stops/restarts containers and tails logs through the scoped Docker socket proxy
- Runs every
exacommand except the CLI-only ones from the CLI Console, in an isolated subprocess (POST /api/v1/cli/runs) - Proxies copilot questions to Skipper's OpenAI-compatible bridge (POST /api/v1/copilot/ask, propose-only)
- Starts JupyterHub named servers for project workbenches; enforces JWT viewer/admin auth and audits most mutations (approvals are recorded by the control plane instead)
Talks to: Control plane API (HTTP); MLflow tracking server + model registry (HTTP (/ajax-api)); Prefect server (orchestrator) (HTTP); Ray Serve MultiModelServer (HTTP); Prometheus (HTTP (PromQL)); Grafana (HTTP/iframe); Loki (HTTP); MinIO object store (S3); Skipper agent server (web UI + OpenAI-compatible bridge) (HTTP (OpenAI-compatible)); Scoped Docker socket proxy (Docker API over TCP 2375); JupyterHub (+ spawned JupyterLab) (HTTP (Hub API)); SeanerBUS bridge (HTTP); PostgreSQL (MLflow + Prefect metadata) (PostgreSQL (asyncpg)); Shared platform datastore (platform.db) (SQL)
Keeps: Postgres tables dashboard_config, dashboard_audit, model_doc_overrides, model_doc_images; bucket dashboard-model-docs; platform.db via /state bind mount
Operate: make dashboard-up ; make dashboard-check ; UI http://localhost:18099
Guide: index
Scoped Docker socket proxy¶
Port: internal only
Built on: tecnativa/docker-socket-proxy:0.2.0, internal only (2375)
What it does
- Holds /var/run/docker.sock read-only on behalf of the dashboard
- Opens only the containers API section (list, inspect, logs, and write methods such as start, stop and restart)
- Denies images, exec, volumes, networks, secrets, swarm, build and the rest
Operate: make stack-up (service: docker-socket-proxy)
Guide: production hardening
Skipper agent server (web UI + OpenAI-compatible bridge)¶
Port: 18004
Built on: LangGraph/LangChain ReAct agent behind FastAPI/uvicorn (platform/services/agent/agent_server.py); binds 127.0.0.1:18004
What it does
- Serves POST /v1/chat/completions (SSE when stream=true) used by
exa askand the dashboard copilot - Serves a chat web UI at /, a WebSocket chat at /ws/chat/{thread_id}, thread history and memory admin APIs
- Calls platform tools: registry, inference, metrics, training, approvals, modelzoo, services, pipelines, docs, knowledge, platform_ops, finops and the shared MCP tool registry (the default supervisor takes its read tools from it; the single-agent fallback uses it unless
AGENT_USE_MCP_TOOLS=false) - Gates mutating tools with a LangGraph interrupt() HITL step and signed, expiring action IDs
- Keeps short-term conversation checkpoints and long-term memory (sqlite-vec store with local embeddings)
- Picks the LLM backend in order Azure OpenAI -> Claude API -> Ollama
Talks to: Ollama LLM backend (external) (HTTP); Control plane API (HTTP); MLflow tracking server + model registry (HTTP); Ray Serve MultiModelServer (HTTP); Prometheus (HTTP (PromQL)); Dashboard (FastAPI BFF + React SPA) (HTTP); Shared platform datastore (platform.db) (SQL)
Keeps: agent_memory.db (checkpoints); skipper_memory.db (long-term store); skipper_review.db (memory review queue); volume agent_data
Operate: make stack-up (service: agent) or make skipper-server ; CLI: make skipper ; exa ask "..."
Guide: agent
Skipper-watch monitoring daemon¶
Port: internal only
Built on: python -m skipper.watch (--once or --daemon), LLM-free loop
What it does
- Scans prediction drift against baseline using a z-score threshold
- Checks platform cost against budget
- On breach, enqueues an outbox event, writes a hash-chained audit event and records an episodic memory
- Holds a cross-process lock so only one daemon is active across replicas
Talks to: Shared platform datastore (platform.db) (SQL)
Operate: make skipper-watch [ARGS=--dry-run]
Guide: agent
ExaMLOps MCP server¶
Port: 8765
Built on: FastMCP (optional extra examlops[mcp]); stdio default, HTTP on loopback 127.0.0.1:8765
What it does
- Exposes the examlops.mcp tool registry (status, models, drift, traffic, SLO, FinOps, gateway, HPC, projects) to MCP clients
- Publishes resources (examlops://status, models, audit/recent, model/{name}) and prompts (diagnose_drift, promote_safely, platform_triage)
- Registers mutating tools (e.g. trigger_retrain, set_traffic_split, hpc_approve_cluster) only with --allow-writes / EXAMLOPS_MCP_ALLOW_WRITES
- Refuses to bind HTTP to a non-loopback host (no built-in auth)
- Emits an A2A Agent Card via
exa mcp agent-card
Talks to: MLflow tracking server + model registry (HTTP); Control plane API (HTTP); Shared platform datastore (platform.db) (SQL)
Operate: exa mcp serve [--transport http --port 8765] [--allow-writes] ; exa mcp tools
Guide: agent
JupyterHub (+ spawned JupyterLab)¶
Port: 18888
Built on: JupyterHub + DockerSpawner + NativeAuthenticator (Dockerfile.jupyterhub; user image examlops-jupyterlab); compose profile jupyter
What it does
- Authenticates users with NativeAuthenticator (open signup off, admin user 'admin')
- Spawns one examlops-jupyterlab container per login on the compose network via DockerSpawner
- Mounts a per-project shared volume (examlops-project-
-shared -> /project) on spawn - Allows up to 10 named servers per user, used as project workbenches
- Registers a 'dashboard' service token with admin:servers scopes so the dashboard can start/stop workbenches
Talks to: MinIO object store (S3)
Keeps: volume jupyter_hub_data
Operate: make jupyter-up ; make jupyter-add-user USER=<name> HUB_TOKEN=<token>
Guide: jupyter
Ollama LLM backend (external)¶
Port: 11436
Built on: External Ollama server; container default http://host.docker.internal:11436 (tunnel); Skipper defaults to 11436, while a stock local ollama serve listens on 11434 — set AGENT_OLLAMA_URL
What it does
- Runs the agent's default chat model (AGENT_MODEL, default llama3.1:8b)
- Provides local embeddings for Skipper memory (nomic-embed-text, 768 dims)
Operate: external; configure AGENT_OLLAMA_URL / AGENT_CONTAINER_OLLAMA_URL
Guide: agent
Observability¶
Metrics, alerts, logs and traces.
Prometheus¶
Port: 19090
Built on: prom/prometheus:v2.51.0 (Dockerfile.prometheus); profile monitoring; 7d retention
What it does
- Scrapes every 15s: ray-serving:8080, control-plane:8002, seanerbus-bridge:8003, alertmanager, tempo, loki, vllm
- Loads fleet targets (node_exporter/DCGM/vLLM) from file_sd JSON refreshed every 30s, generated by
exa hpc prometheus-sd - Evaluates 31 alert rules from alert_rules.yml and sends alerts to Alertmanager
- Stamps external labels cluster/tenant on every series
Talks to: Ray Serve MultiModelServer (HTTP scrape); Control plane API (HTTP scrape); SeanerBUS bridge (HTTP scrape); Loki (HTTP scrape); Grafana Tempo (HTTP scrape); vLLM OpenAI-compatible server (HTTP scrape); Alertmanager (HTTP)
Keeps: volume prometheus_data
Operate: make monitoring-up ; make alerts-check
Guide: grafana
Alertmanager¶
Port: 19093
Built on: prom/alertmanager:v0.27.0; profile monitoring
What it does
- Routes alerts grouped by alertname/cluster/service
- Sends critical alerts to PagerDuty + Slack and warnings to Slack (secrets read from files; missing file = no-op receiver)
- Delivers a heartbeat webhook proving the Prometheus -> Alertmanager path works
- Applies inhibition rules and silences
Keeps: volume alertmanager_data
Operate: make monitoring-up
Guide: grafana
Grafana¶
Port: 13000
Built on: grafana/grafana:10.4.0 (Dockerfile.grafana); bound to 127.0.0.1 by default; profile monitoring
What it does
- Auto-provisions Prometheus, Loki and Tempo datasources
- Auto-provisions 7 dashboards: overview, online metrics, control plane, drift, approvals, logs, seanerbus
- Allows embedding so the dashboard and Ray dashboard can iframe panels
- Keeps anonymous access off unless GRAFANA_ANONYMOUS_ENABLED=true
Talks to: Prometheus (HTTP (PromQL)); Loki (HTTP (LogQL)); Grafana Tempo (HTTP)
Keeps: volume grafana_data
Operate: make monitoring-up ; http://localhost:13000
Guide: grafana
Loki¶
Port: 13100
Built on: grafana/loki:2.9.10, filesystem storage; profile monitoring
What it does
- Stores container logs pushed by Promtail
- Keeps logs for 168h (7 days) with retention/compaction enabled
- Answers LogQL queries from Grafana and the dashboard
Keeps: volume loki_data
Operate: make monitoring-up
Guide: grafana
Promtail¶
Port: internal only
Built on: grafana/promtail:2.9.10; profile monitoring
What it does
- Discovers containers through the Docker socket (docker_sd_configs)
- Tails container stdout/stderr with no per-service code change
- Labels streams with project, compose_service, container and stream
- Pushes to Loki /loki/api/v1/push
Talks to: Loki (HTTP push)
Keeps: volume promtail_data (positions)
Operate: make monitoring-up
Guide: grafana
Grafana Tempo¶
Port: 13200
Built on: grafana/tempo:2.5.0; OTLP receivers 4317 (gRPC) / 4318 (HTTP) on the compose network; profile monitoring
What it does
- Receives OTLP traces from control plane, dashboard, ray-serving (OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317)
- Keeps trace blocks for 48h
- Serves trace queries to Grafana on 3200
Keeps: volume tempo_data
Operate: make monitoring-up ; enable export with OTEL_SDK_DISABLED=false
Guide: grafana
OpenTelemetry Collector (production config, not in compose)¶
Port: internal only
Built on: Config file only (otel-collector-config.yml); no compose service or Make target starts it
What it does
- Receives OTLP on 4317 (gRPC) / 4318 (HTTP)
- Tail-samples traces: keeps all errors and traces >1000 ms, samples 5% of the rest
- Batches (5s / 1024) with a memory limiter at 80%
- Fans traces to Tempo and metrics to a Prometheus remote-write target
Talks to: Grafana Tempo (OTLP)
Operate: not started by the dev stack; config guarded by tests/unit/test_otel_collector_config.py