From 03af39def9a2e1ad5204933495d3e93fb99ca087 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 15:50:33 +0000 Subject: [PATCH] feat(docker): core compose + entrypoint (MS22-P1j) (#613) Co-authored-by: Jason Woltje Co-committed-by: Jason Woltje --- docker/.env.example | 3 ++ docker/README.md | 40 +++++++++++++++++++++++++ docker/mosaic-compose.yml | 53 ++++++++++++++++++++++++++++++++++ docker/openclaw-entrypoint.sh | 20 +++++++++++++ docker/openclaw-healthcheck.sh | 2 ++ 5 files changed, 118 insertions(+) create mode 100644 docker/.env.example create mode 100644 docker/README.md create mode 100644 docker/mosaic-compose.yml create mode 100755 docker/openclaw-entrypoint.sh create mode 100755 docker/openclaw-healthcheck.sh diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..de3ecd8 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL=postgresql://mosaic:changeme@postgres:5432/mosaic +DATABASE_PASSWORD=changeme +MOSAIC_SECRET_KEY=your-secret-key-at-least-32-characters-long diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..ec106e7 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,40 @@ +# Mosaic Docker (Core Services) + +This folder includes the Compose stack for **core Mosaic services only**: + +- `mosaic-api` +- `mosaic-web` +- `postgres` + +User OpenClaw containers are **not** defined in Compose. They are created and managed dynamically by the API's `ContainerLifecycleService` through Docker socket access. + +## Start the stack + +```bash +docker compose -f docker/mosaic-compose.yml up -d +``` + +## Required environment variables + +- `DATABASE_URL` +- `MOSAIC_SECRET_KEY` +- `DATABASE_PASSWORD` + +Use [`docker/.env.example`](./.env.example) as a starting point. + +## Architecture overview + +See the design doc: [`docs/design/MS22-DB-CENTRIC-ARCHITECTURE.md`](../docs/design/MS22-DB-CENTRIC-ARCHITECTURE.md) + +`mosaic-agents` is an internal-only bridge network reserved for dynamically created user containers. + +## OpenClaw entrypoint behavior + +`docker/openclaw-entrypoint.sh` is intended for dynamically created user OpenClaw containers: + +1. Validates required env vars (`MOSAIC_API_URL`, `AGENT_TOKEN`, `AGENT_ID`). +2. Fetches agent-specific OpenClaw config from Mosaic API internal endpoint. +3. Writes the config to `/tmp/openclaw.json`. +4. Starts OpenClaw gateway with `OPENCLAW_CONFIG_PATH=/tmp/openclaw.json`. + +`docker/openclaw-healthcheck.sh` probes `http://localhost:18789/health` for container health. diff --git a/docker/mosaic-compose.yml b/docker/mosaic-compose.yml new file mode 100644 index 0000000..e3dc7ca --- /dev/null +++ b/docker/mosaic-compose.yml @@ -0,0 +1,53 @@ +services: + mosaic-api: + image: mosaic/api:latest + environment: + DATABASE_URL: ${DATABASE_URL} + MOSAIC_SECRET_KEY: ${MOSAIC_SECRET_KEY} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:4000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + mosaic-web: + image: mosaic/web:latest + environment: + NEXT_PUBLIC_API_URL: http://mosaic-api:4000 + ports: + - "3000:3000" + networks: + - internal + depends_on: + mosaic-api: + condition: service_healthy + + postgres: + image: postgres:17-alpine + environment: + POSTGRES_DB: mosaic + POSTGRES_USER: mosaic + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - internal + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mosaic"] + interval: 10s + timeout: 5s + retries: 5 + +networks: + internal: + driver: bridge + mosaic-agents: + driver: bridge + internal: true + +volumes: + postgres-data: diff --git a/docker/openclaw-entrypoint.sh b/docker/openclaw-entrypoint.sh new file mode 100755 index 0000000..884525d --- /dev/null +++ b/docker/openclaw-entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e +: "${MOSAIC_API_URL:?MOSAIC_API_URL is required}" +: "${AGENT_TOKEN:?AGENT_TOKEN is required}" +: "${AGENT_ID:?AGENT_ID is required}" + +echo "[entrypoint] Fetching config for agent ${AGENT_ID}..." +HTTP_CODE=$(curl -sf -w "%{http_code}" \ + "${MOSAIC_API_URL}/api/internal/agent-config/${AGENT_ID}" \ + -H "Authorization: Bearer ${AGENT_TOKEN}" \ + -o /tmp/openclaw.json) + +if [ "$HTTP_CODE" != "200" ]; then + echo "[entrypoint] ERROR: Config fetch failed with HTTP ${HTTP_CODE}" + exit 1 +fi + +echo "[entrypoint] Config loaded. Starting OpenClaw gateway..." +export OPENCLAW_CONFIG_PATH=/tmp/openclaw.json +exec openclaw gateway run --bind lan --auth token diff --git a/docker/openclaw-healthcheck.sh b/docker/openclaw-healthcheck.sh new file mode 100755 index 0000000..83e1108 --- /dev/null +++ b/docker/openclaw-healthcheck.sh @@ -0,0 +1,2 @@ +#!/bin/sh +curl -sf http://localhost:18789/health || exit 1