149 lines
4.9 KiB
YAML
149 lines
4.9 KiB
YAML
|
|
# AI Cluster — target node: SOLARIA (compute/GPU).
|
||
|
|
#
|
||
|
|
# Authored from the live VPS stack at /home/dockeruser/docker/ai-cluster/ (read-only
|
||
|
|
# recon 2026-07-27). The VPS stack is still running and is NOT touched by this file;
|
||
|
|
# see CUTOVER.md for the switch-over plan.
|
||
|
|
#
|
||
|
|
# Deliberate differences from the VPS original:
|
||
|
|
# - images built from ./src/* instead of pre-built local `ai-cluster-*` images
|
||
|
|
# - openclaw publishes on SOLARIA's Tailscale IP only (was 0.0.0.0:8000)
|
||
|
|
# - the external `npm_default` network is gone (it does not exist on SOLARIA;
|
||
|
|
# NPM on the VPS will proxy to openclaw over Tailscale instead)
|
||
|
|
# - mosquitto binds SOLARIA's Tailscale IP (was the VPS Tailscale IP)
|
||
|
|
# - telegram-bot service is not carried over (it does not run on the VPS either)
|
||
|
|
# - secrets come from env_file, not from ${} interpolation
|
||
|
|
#
|
||
|
|
# Secrets: /opt/homelab/config/ai-cluster/.env (see env.example). Compose validates
|
||
|
|
# env_file paths at parse time, which is why service-ops-worker mounts that same
|
||
|
|
# directory at the same absolute path — otherwise `docker compose ps` inside that
|
||
|
|
# container fails to parse this file.
|
||
|
|
|
||
|
|
services:
|
||
|
|
redis:
|
||
|
|
image: redis:7-alpine
|
||
|
|
restart: unless-stopped
|
||
|
|
volumes:
|
||
|
|
- redis_data:/data
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
mosquitto:
|
||
|
|
image: eclipse-mosquitto:2
|
||
|
|
container_name: mosquitto
|
||
|
|
restart: unless-stopped
|
||
|
|
ports:
|
||
|
|
# SOLARIA Tailscale IP. Never 0.0.0.0 — the broker has auth but is not
|
||
|
|
# meant to be reachable off-mesh.
|
||
|
|
- "100.100.231.104:1883:1883"
|
||
|
|
volumes:
|
||
|
|
# mosquitto.conf + acl ship in src/mosquitto/ and are copied to this path
|
||
|
|
# at deploy; `passwd` is generated on the node and never committed.
|
||
|
|
- /opt/homelab/config/ai-cluster/mosquitto:/mosquitto/config:ro
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
openclaw:
|
||
|
|
build:
|
||
|
|
context: ./src/openclaw
|
||
|
|
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||
|
|
restart: unless-stopped
|
||
|
|
env_file:
|
||
|
|
- /opt/homelab/config/ai-cluster/.env
|
||
|
|
environment:
|
||
|
|
REDIS_URL: redis://redis:6379/0
|
||
|
|
MQTT_HOST: mosquitto
|
||
|
|
MQTT_PORT: 1883
|
||
|
|
ports:
|
||
|
|
# Tailscale-only. NPM on the VPS proxies to 100.100.231.104:8000 over the
|
||
|
|
# mesh — see CUTOVER.md. `healthcheck.sh` therefore probes the Tailscale
|
||
|
|
# IP, not localhost.
|
||
|
|
- "100.100.231.104:8000:8000"
|
||
|
|
healthcheck:
|
||
|
|
# The image is python:3.12-slim: no wget, no curl (verified against the
|
||
|
|
# running VPS image). urllib is the only thing available.
|
||
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"]
|
||
|
|
interval: 10s
|
||
|
|
timeout: 5s
|
||
|
|
retries: 5
|
||
|
|
depends_on:
|
||
|
|
- redis
|
||
|
|
- mosquitto
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
codex-worker:
|
||
|
|
# Builds from ./src/worker (default Dockerfile → worker.py). The VPS also has
|
||
|
|
# a stale `codex-worker/worker.py` prototype that nothing builds — not carried
|
||
|
|
# over; see service.yaml notes.
|
||
|
|
build:
|
||
|
|
context: ./src/worker
|
||
|
|
restart: unless-stopped
|
||
|
|
env_file:
|
||
|
|
- /opt/homelab/config/ai-cluster/.env
|
||
|
|
environment:
|
||
|
|
# AGENT_ID/ROLE are MQTT routing targets. Kept as `vps-*` on purpose so
|
||
|
|
# existing task producers keep working across the move; renaming is a
|
||
|
|
# separate, coordinated change (CUTOVER.md, follow-up).
|
||
|
|
AGENT_ID: vps-dev-1
|
||
|
|
ROLE: dev
|
||
|
|
REDIS_URL: redis://redis:6379/0
|
||
|
|
MQTT_HOST: mosquitto
|
||
|
|
MQTT_PORT: 1883
|
||
|
|
REQUEST_TIMEOUT_SECONDS: 30
|
||
|
|
depends_on:
|
||
|
|
- redis
|
||
|
|
- mosquitto
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
planner-worker:
|
||
|
|
build:
|
||
|
|
context: ./src/worker
|
||
|
|
dockerfile: Dockerfile.planner
|
||
|
|
restart: unless-stopped
|
||
|
|
env_file:
|
||
|
|
- /opt/homelab/config/ai-cluster/.env
|
||
|
|
environment:
|
||
|
|
AGENT_ID: vps-planner-1
|
||
|
|
ROLE: planner
|
||
|
|
MQTT_HOST: mosquitto
|
||
|
|
MQTT_PORT: 1883
|
||
|
|
depends_on:
|
||
|
|
- mosquitto
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
service-ops-worker:
|
||
|
|
build:
|
||
|
|
context: ./src/worker
|
||
|
|
dockerfile: Dockerfile.service-ops
|
||
|
|
restart: unless-stopped
|
||
|
|
env_file:
|
||
|
|
- /opt/homelab/config/ai-cluster/.env
|
||
|
|
environment:
|
||
|
|
AGENT_ID: vps-service-ops-1
|
||
|
|
ROLE: service-ops
|
||
|
|
MQTT_HOST: mosquitto
|
||
|
|
MQTT_PORT: 1883
|
||
|
|
COMPOSE_PROJECT_NAME: ai-cluster
|
||
|
|
volumes:
|
||
|
|
# This worker shells out to `docker compose ps|restart` from /app, so it
|
||
|
|
# needs the compose file and a .env in its cwd for interpolation.
|
||
|
|
# The repo-path mount is host-specific → hosts/solaria/runtime/ai-cluster/.
|
||
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
||
|
|
- /opt/homelab/config/ai-cluster/.env:/app/.env:ro
|
||
|
|
# Same absolute path as the env_file directive above, so compose can parse
|
||
|
|
# the mounted compose file from inside this container.
|
||
|
|
- /opt/homelab/config/ai-cluster:/opt/homelab/config/ai-cluster:ro
|
||
|
|
depends_on:
|
||
|
|
- mosquitto
|
||
|
|
networks:
|
||
|
|
- ai-cluster
|
||
|
|
|
||
|
|
volumes:
|
||
|
|
redis_data:
|
||
|
|
|
||
|
|
networks:
|
||
|
|
ai-cluster:
|
||
|
|
driver: bridge
|