5.2.43. Ray¶
Distributed-compute substrate for the stack. Ray runs as a head + worker cluster reachable from JupyterHub, Backend (via REST), and any host Python via ray.init("ray://localhost:<RAY_CLIENT_PORT>").
1. Overview¶
Ray (rayproject/ray:2.56.0, Apache 2.0) is a generic parallel-compute framework. This stack ships it as a 2-container family (head + workers) wired so every tier can dispatch parallel work without rolling its own asyncio.gather glue. Use Ray when you have N independent units of work to fan out across CPUs (and eventually GPUs on multi-host Linux).
Active when RAY_SOURCE ∈ {ray-container-cpu, ray-container-gpu}. Authenticated remote Ray endpoints (Anyscale, self-hosted clusters) are deferred to the stack-wide authenticated-remote design.
2. Access¶
| Surface | URL | Auth |
|---|---|---|
| Dashboard (UI + REST job-submission API) | http://localhost:${RAY_DASHBOARD_PORT} direct or http://ray.localhost:${KONG_HTTP_PORT} via Kong |
Direct: unauthenticated and bound to loopback only. Kong: basic-auth with DASHBOARD_USERNAME / DASHBOARD_PASSWORD. |
| Client server (trusted host Python) | ray://localhost:${RAY_CLIENT_PORT} |
Unauthenticated; bound to loopback only. Never forward or publicly expose this port. |
| GCS (internal cluster controller) | localhost:${RAY_GCS_PORT} host-side; ray-head:6379 inside the network |
Unauthenticated; host mapping is loopback only. |
| Backend REST jobs API | http://localhost:${BACKEND_PORT}/api/ray/jobs/submit etc. |
Bearer token from RAY_JOB_API_TOKEN |
3. Configuration¶
| Env var | Default | When | Description |
|---|---|---|---|
RAY_SOURCE |
disabled |
always | One of ray-container-cpu, ray-container-gpu, disabled. |
RAY_WORKER_COUNT |
2 |
when source ∈ | Number of ray-worker containers. Use 0 for head-only single-node mode. No hard upper bound — bounded by host RAM and CPUs. |
RAY_DASHBOARD_PORT, RAY_GCS_PORT, RAY_CLIENT_PORT |
auto-assigned | always | Topology-allocated in the infra block and published only on 127.0.0.1. |
RAY_JOB_API_TOKEN |
auto-generated | always | Required as Authorization: Bearer <token> on every Backend /api/ray route. Stored in .env and injected only into Backend. |
RAY_IMAGE, RAY_GPU_IMAGE, RAY_HEAD_SCALE, RAY_WORKER_SCALE, RAY_ADDRESS |
auto-managed | always | Resolved by _generate_ray_config() from RAY_SOURCE + RAY_WORKER_COUNT. Don't edit by hand. |
Wizard behavior: when the user selects ray-container-cpu or ray-container-gpu, the wizard then prompts for RAY_WORKER_COUNT (integer, default 2) inline on the source step via the SecondaryNumberInput widget.
4. Architecture & wiring¶
Containers in the family:
- ray-head — the cluster controller. Runs ray start --head. Exposes ports 8265 (dashboard + REST), 6379 (GCS — Ray's internal cluster controller, distinct from the project's Redis cache despite both using Redis wire protocol), 10001 (client server). Healthcheck on :8265/api/version.
- ray-worker — one or more replicas. Runs ray start --address=ray-head:6379 --block. No host ports.
Why no /tmp/ray volume: Ray spills object-store state to /tmp/ray per node, but the fragments deliberately mount no named volume there. The rayproject/ray image runs as the non-root ray user and doesn't pre-create /tmp/ray, so a named Docker volume would be initialized root:root and become unwritable by ray — Ray would then fail to start. Session state therefore lives in the container's writable layer (per-run, ephemeral), which is the intended behavior; /dev/shm is sized via shm_size to avoid the object-store spill in the first place.
Critical shared memory: Both containers set shm_size: 8gb — Docker's default 64MB causes immediate crash because Ray's Plasma object store needs shared memory. If you see startup failures with "Connection refused" on port 8265 within 60 seconds, check shm size.
No external runtime dependencies. Ray ships its own GCS (Redis-protocol cluster controller) and Plasma (shared-memory object store). The cluster is fully self-contained. The supabase + redis entries in this manifest's depends_on.required are display-ordering pins (so Kong wins the alphabetical tie within the infra port-slot block), NOT runtime calls — Ray does not actually talk to either at runtime.
Consumers in the stack:
- Backend — exposes POST /api/ray/jobs, GET/DELETE /api/ray/jobs/{job_id}, and GET /api/ray/cluster/status. It adapts via RAY_ADDRESS set by _generate_ray_config() and requires RAY_JOB_API_TOKEN as a bearer token on every route.
- JupyterHub — notebooks can import ray; ray.init() directly (RAY_ADDRESS picked up from env). Sample notebook: services/jupyterhub/build/notebooks/07_ray_cluster.ipynb (mounted read-only at /home/jovyan/notebooks/ inside the JupyterHub container).
- Hermes — no Ray submission integration is wired today. A future integration must receive RAY_JOB_API_TOKEN through a scoped client contract before it can call Backend's protected Ray routes.
5. Dependencies & Integrations¶
5.1. Current — Upstream (this service calls)¶
No upstream calls.
5.2. Current — Downstream (services that call this)¶
| Service | Category |
|---|---|
| kong | infra |
| backend | apps |
| jupyterhub | apps |
5.3. Architecture diagram¶
Open the full-size diagram for a full-screen view.
5.4. Future — Missing pair integrations¶
No high-confidence opportunities identified.
5.5. Future — Candidate new services¶
No high-confidence opportunities identified.
5.6. Future — Unused features in this service¶
No high-confidence opportunities identified.
6. Troubleshooting¶
- Head container exits immediately with "Bus error" or "/dev/shm too small" — Docker's default shared-memory size (64MB) is too small. Compose's
shm_size: 8gbshould handle this, but some installs (rootless Podman, older Docker) ignore it. Verify withdocker inspect ${PROJECT_NAME}-ray-head | grep ShmSize. - Workers stuck "starting" — they
depends_on: ray-head: service_healthy. The head'sstart_period: 60sallows up to 60s before health checks count. If still stuck after 2 minutes, check the head's healthcheck output:docker exec ${PROJECT_NAME}-ray-head wget -qO- http://localhost:8265/api/version(the image ships wget, not curl). ray.init("ray://localhost:PORT")from host fails with version mismatch — your host'srayPython package version must match the cluster's image version. Pinray>=2.56.0,<2.57in your host venv to match the image'srayproject/ray:2.56.0.- Dashboard unreachable through Kong — Kong's
ray.localhostroute requires--setup-hoststo have run AND basic-auth credentials matchDASHBOARD_USERNAME/DASHBOARD_PASSWORDin.env. The unauthenticated direct port works only from the Docker host because Compose binds it to127.0.0.1.