From 2abadde949ccc8fb3c719cc2d03129a6936c2468 Mon Sep 17 00:00:00 2001 From: Richard R Date: Sat, 13 Jun 2026 13:21:18 -0600 Subject: [PATCH] docs(deploy): add unified Docker Compose guide and update dev workflow Introduce a comprehensive Docker Compose documentation page covering slim, full, and local-build stack examples. Add example Compose files under docker/examples/ for streamlined deployment. Update local development docs to reference the new Compose guide. Revise package.json scripts to support the new Compose workflows and remove legacy compute-worker compose commands. Update sidebar to include the Docker Compose documentation for improved discoverability. --- Dockerfile | 6 +- compute-worker/docker-compose.yml | 44 ------ docker/examples/compose.full.yml | 124 +++++++++++++++++ docker/examples/compose.local.yml | 130 +++++++++++++++++ docker/examples/compose.yml | 49 +++++++ docs-site/docs/deploy/docker-compose.md | 153 +++++++++++++++++++++ docs-site/docs/deploy/local-development.md | 43 +----- docs-site/sidebars.ts | 7 +- package.json | 12 +- 9 files changed, 477 insertions(+), 91 deletions(-) delete mode 100644 compute-worker/docker-compose.yml create mode 100644 docker/examples/compose.full.yml create mode 100644 docker/examples/compose.local.yml create mode 100644 docker/examples/compose.yml create mode 100644 docs-site/docs/deploy/docker-compose.md diff --git a/Dockerfile b/Dockerfile index f4ebc3e..83230da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,12 +73,14 @@ COPY --from=app-builder /app/src/db ./src/db # Merge in the dependency subset needed by the entrypoint migration scripts. COPY --from=app-builder /opt/entrypoint-migration-tools/node_modules /tmp/runtime-tools-node_modules RUN mkdir -p /app/node_modules && \ + rm -rf /app/node_modules/pg && \ rm -rf /tmp/runtime-tools-node_modules/@aws-sdk \ /tmp/runtime-tools-node_modules/better-sqlite3 \ - /tmp/runtime-tools-node_modules/ffmpeg-static \ - /tmp/runtime-tools-node_modules/pg && \ + /tmp/runtime-tools-node_modules/ffmpeg-static && \ cp -an /tmp/runtime-tools-node_modules/. /app/node_modules/ && \ rm -rf /tmp/runtime-tools-node_modules +# Drizzle Kit discovers the Postgres driver through ESM import during startup migrations. +RUN node --input-type=module -e "await import('pg')" # Ship the embedded compute worker as a separate deployed bundle. COPY --from=app-builder /opt/embedded-compute-worker ./embedded-compute-worker diff --git a/compute-worker/docker-compose.yml b/compute-worker/docker-compose.yml deleted file mode 100644 index 115eea8..0000000 --- a/compute-worker/docker-compose.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - nats: - image: nats:2.14-alpine - container_name: openreader-compute-nats - command: ["-js", "-sd", "/data"] - ports: - - "4222:4222" - - "8222:8222" - volumes: - - nats-data:/data - - compute-worker: - build: - context: .. - dockerfile: compute-worker/Dockerfile - container_name: openreader-compute-worker - depends_on: - - nats - env_file: - - path: ./.env - required: false - environment: - NATS_URL: ${NATS_URL:-nats://nats:4222} - COMPUTE_WORKER_HOST: ${COMPUTE_WORKER_HOST:-0.0.0.0} - PORT: ${PORT:-8081} - COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token} - S3_PREFIX: ${S3_PREFIX:-openreader} - COMPUTE_PREWARM_MODELS: ${COMPUTE_PREWARM_MODELS:-false} - ports: - - "8081:8081" - develop: - watch: - - action: sync+restart - path: . - target: /workspace - - action: rebuild - path: ./package.json - - action: rebuild - path: ../pnpm-lock.yaml - - action: rebuild - path: ../pnpm-workspace.yaml - -volumes: - nats-data: diff --git a/docker/examples/compose.full.yml b/docker/examples/compose.full.yml new file mode 100644 index 0000000..bdf2270 --- /dev/null +++ b/docker/examples/compose.full.yml @@ -0,0 +1,124 @@ +name: openreader-example + +services: + openreader: + image: ghcr.io/richardr1126/openreader:latest + # Keep localhost:8333 valid for both app requests and browser-facing presigned URLs. + network_mode: service:seaweedfs + depends_on: + kokoro-tts: + condition: service_started + postgres: + condition: service_healthy + seaweedfs: + condition: service_healthy + environment: + BASE_URL: ${BASE_URL:-http://localhost:3003} + AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me} + POSTGRES_URL: postgres://openreader:openreader@postgres:5432/openreader + COMPUTE_WORKER_URL: http://compute-worker:8081 + COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token} + USE_EMBEDDED_WEED_MINI: "false" + S3_ENDPOINT: ${S3_ENDPOINT:-http://localhost:8333} # Used for internal endpoint and public facing presigned URLs. + S3_BUCKET: ${S3_BUCKET:-openreader-documents} + S3_REGION: ${S3_REGION:-us-east-1} + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + S3_FORCE_PATH_STYLE: "true" + S3_PREFIX: ${S3_PREFIX:-openreader} + RUNTIME_SEED_JSON: |- + { + "version": 1, + "runtimeConfig": { + "defaultTtsProvider": "kokoro" + }, + "providers": [ + { + "slug": "kokoro", + "displayName": "Kokoro", + "providerType": "custom-openai", + "baseUrl": "http://kokoro-tts:8880/v1", + "defaultModel": "kokoro", + "enabled": true + } + ] + } + volumes: + - openreader-docstore:/app/docstore + + kokoro-tts: + image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4 + environment: + ONNX_NUM_THREADS: 8 + ONNX_INTER_OP_THREADS: 4 + ONNX_EXECUTION_MODE: parallel + ONNX_OPTIMIZATION_LEVEL: all + ONNX_MEMORY_PATTERN: "true" + ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo + API_LOG_LEVEL: DEBUG + ports: + - "8880:8880" + + seaweedfs: + image: chrislusf/seaweedfs:4.18 + command: ["mini", "-dir=/data"] + environment: + AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + AWS_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null"] + interval: 2s + timeout: 2s + retries: 30 + ports: + - "3003:3003" + - "8333:8333" + volumes: + - seaweedfs-data:/data + + nats: + image: nats:2.14-alpine + command: ["-js", "-sd", "/data"] + volumes: + - nats-data:/data + + postgres: + image: postgres:17-alpine + environment: + POSTGRES_USER: openreader + POSTGRES_PASSWORD: openreader + POSTGRES_DB: openreader + healthcheck: + test: ["CMD-SHELL", "pg_isready -U openreader -d openreader"] + interval: 2s + timeout: 2s + retries: 30 + volumes: + - postgres-data:/var/lib/postgresql/data + + compute-worker: + image: ghcr.io/richardr1126/openreader-compute-worker:latest + depends_on: + nats: + condition: service_started + seaweedfs: + condition: service_healthy + environment: + NATS_URL: nats://nats:4222 + COMPUTE_WORKER_HOST: 0.0.0.0 + PORT: 8081 + COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token} + S3_ENDPOINT: http://seaweedfs:8333 + S3_BUCKET: ${S3_BUCKET:-openreader-documents} + S3_REGION: ${S3_REGION:-us-east-1} + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + S3_FORCE_PATH_STYLE: "true" + S3_PREFIX: ${S3_PREFIX:-openreader} + COMPUTE_PREWARM_MODELS: ${COMPUTE_PREWARM_MODELS:-false} + +volumes: + openreader-docstore: + seaweedfs-data: + nats-data: + postgres-data: diff --git a/docker/examples/compose.local.yml b/docker/examples/compose.local.yml new file mode 100644 index 0000000..a50b667 --- /dev/null +++ b/docker/examples/compose.local.yml @@ -0,0 +1,130 @@ +name: openreader-local-build + +services: + openreader: + image: openreader:local + build: + context: ../.. + dockerfile: Dockerfile + # Keep localhost:8333 valid for both app requests and browser-facing presigned URLs. + network_mode: service:seaweedfs + depends_on: + kokoro-tts: + condition: service_started + postgres: + condition: service_healthy + seaweedfs: + condition: service_healthy + environment: + BASE_URL: ${BASE_URL:-http://localhost:3003} + AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me} + POSTGRES_URL: postgres://openreader:openreader@postgres:5432/openreader + COMPUTE_WORKER_URL: http://compute-worker:8081 + COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token} + USE_EMBEDDED_WEED_MINI: "false" + S3_ENDPOINT: ${S3_ENDPOINT:-http://localhost:8333} # Used for internal endpoint and public facing presigned URLs. + S3_BUCKET: ${S3_BUCKET:-openreader-documents} + S3_REGION: ${S3_REGION:-us-east-1} + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + S3_FORCE_PATH_STYLE: "true" + S3_PREFIX: ${S3_PREFIX:-openreader} + RUNTIME_SEED_JSON: |- + { + "version": 1, + "runtimeConfig": { + "defaultTtsProvider": "kokoro" + }, + "providers": [ + { + "slug": "kokoro", + "displayName": "Kokoro", + "providerType": "custom-openai", + "baseUrl": "http://kokoro-tts:8880/v1", + "defaultModel": "kokoro", + "enabled": true + } + ] + } + volumes: + - openreader-docstore:/app/docstore + + kokoro-tts: + image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4 + environment: + ONNX_NUM_THREADS: 8 + ONNX_INTER_OP_THREADS: 4 + ONNX_EXECUTION_MODE: parallel + ONNX_OPTIMIZATION_LEVEL: all + ONNX_MEMORY_PATTERN: "true" + ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo + API_LOG_LEVEL: DEBUG + ports: + - "8880:8880" + + seaweedfs: + image: chrislusf/seaweedfs:4.18 + command: ["mini", "-dir=/data"] + environment: + AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + AWS_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null"] + interval: 2s + timeout: 2s + retries: 30 + ports: + - "3003:3003" + - "8333:8333" + volumes: + - seaweedfs-data:/data + + nats: + image: nats:2.14-alpine + command: ["-js", "-sd", "/data"] + volumes: + - nats-data:/data + + postgres: + image: postgres:17-alpine + environment: + POSTGRES_USER: openreader + POSTGRES_PASSWORD: openreader + POSTGRES_DB: openreader + healthcheck: + test: ["CMD-SHELL", "pg_isready -U openreader -d openreader"] + interval: 2s + timeout: 2s + retries: 30 + volumes: + - postgres-data:/var/lib/postgresql/data + + compute-worker: + image: openreader-compute-worker:local + build: + context: ../.. + dockerfile: compute-worker/Dockerfile + depends_on: + nats: + condition: service_started + seaweedfs: + condition: service_healthy + environment: + NATS_URL: nats://nats:4222 + COMPUTE_WORKER_HOST: 0.0.0.0 + PORT: 8081 + COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token} + S3_ENDPOINT: http://seaweedfs:8333 + S3_BUCKET: ${S3_BUCKET:-openreader-documents} + S3_REGION: ${S3_REGION:-us-east-1} + S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey} + S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret} + S3_FORCE_PATH_STYLE: "true" + S3_PREFIX: ${S3_PREFIX:-openreader} + COMPUTE_PREWARM_MODELS: ${COMPUTE_PREWARM_MODELS:-false} + +volumes: + openreader-docstore: + seaweedfs-data: + nats-data: + postgres-data: diff --git a/docker/examples/compose.yml b/docker/examples/compose.yml new file mode 100644 index 0000000..4745d0e --- /dev/null +++ b/docker/examples/compose.yml @@ -0,0 +1,49 @@ +name: openreader-slim-example + +services: + openreader: + image: ghcr.io/richardr1126/openreader:latest + depends_on: + kokoro-tts: + condition: service_started + environment: + BASE_URL: ${BASE_URL:-http://localhost:3003} + AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me} + RUNTIME_SEED_JSON: |- + { + "version": 1, + "runtimeConfig": { + "defaultTtsProvider": "kokoro" + }, + "providers": [ + { + "slug": "kokoro", + "displayName": "Kokoro", + "providerType": "custom-openai", + "baseUrl": "http://kokoro-tts:8880/v1", + "defaultModel": "kokoro", + "enabled": true + } + ] + } + ports: + - "3003:3003" + - "8333:8333" + volumes: + - openreader-docstore:/app/docstore + + kokoro-tts: + image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4 + environment: + ONNX_NUM_THREADS: 8 + ONNX_INTER_OP_THREADS: 4 + ONNX_EXECUTION_MODE: parallel + ONNX_OPTIMIZATION_LEVEL: all + ONNX_MEMORY_PATTERN: "true" + ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo + API_LOG_LEVEL: DEBUG + ports: + - "8880:8880" + +volumes: + openreader-docstore: diff --git a/docs-site/docs/deploy/docker-compose.md b/docs-site/docs/deploy/docker-compose.md new file mode 100644 index 0000000..78786d3 --- /dev/null +++ b/docs-site/docs/deploy/docker-compose.md @@ -0,0 +1,153 @@ +--- +title: Docker Compose +description: Run OpenReader with the slim, full, or local-build Docker Compose examples. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Use these examples to run OpenReader with Kokoro-FastAPI and persistent storage. Choose the slim +stack for the simplest deployment, or the full stack when you want PostgreSQL, SeaweedFS, NATS, +and the compute worker as separate containers. + +## Prerequisites + +- A recent Docker version with Docker Compose +- A clone of the OpenReader repository + +```bash +git clone https://github.com/richardr1126/openreader.git +cd openreader +``` + +## Choose a stack + + + + +The default slim example runs: + +- OpenReader with embedded SeaweedFS, NATS, compute worker, and SQLite +- Kokoro-FastAPI as a companion container + +```bash +docker compose -f docker/examples/compose.yml up +# Repository convenience command: pnpm compose +``` + +Compose file: [`docker/examples/compose.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.yml) + + + + +The full example runs OpenReader, Kokoro-FastAPI, PostgreSQL, SeaweedFS, NATS, and the compute +worker as separate containers using published images. + +```bash +docker compose -f docker/examples/compose.full.yml up +# Repository convenience command: pnpm compose:full +``` + +Compose file: [`docker/examples/compose.full.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.full.yml) + +For details about running the worker separately, see +[Compute Worker](./compute-worker). + + + + +The local-build example uses the full multi-container layout, but builds the OpenReader app and +compute-worker images from the current checkout. + +```bash +docker compose -f docker/examples/compose.local.yml up --build +# Repository convenience command: pnpm compose:local +``` + +Compose file: [`docker/examples/compose.local.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.local.yml) + + + + +## Included services + +| Service | Slim | Full | Local Build | +| --- | --- | --- | --- | +| OpenReader | Published image | Published image | Local build | +| Kokoro-FastAPI | Container | Container | Container | +| Database | Embedded SQLite | PostgreSQL container | PostgreSQL container | +| SeaweedFS | Embedded | Container | Container | +| NATS | Embedded | Container | Container | +| Compute worker | Embedded | Published image | Local build | + +On first boot, `RUNTIME_SEED_JSON` creates an enabled Kokoro shared provider and selects it as the +default TTS provider. + +## Endpoints + +- OpenReader: `http://localhost:3003` +- SeaweedFS S3: `http://localhost:8333` +- Kokoro-FastAPI: `http://localhost:8880` + +In the full examples, PostgreSQL, the compute worker, and NATS remain internal to the Compose +network. + +## LAN access + +Set `BASE_URL` and `S3_ENDPOINT` to the Docker host's LAN IP so browser-facing app and presigned +S3 URLs are reachable from other devices: + + + + +```bash +BASE_URL=http://192.168.0.XXX:3003 \ +S3_ENDPOINT=http://192.168.0.XXX:8333 \ +docker compose -f docker/examples/compose.yml up +# Repository convenience command: pnpm compose +``` + + + + +```bash +BASE_URL=http://192.168.0.XXX:3003 \ +S3_ENDPOINT=http://192.168.0.XXX:8333 \ +docker compose -f docker/examples/compose.full.yml up +# Repository convenience command: pnpm compose:full +``` + + + + +```bash +BASE_URL=http://192.168.0.XXX:3003 \ +S3_ENDPOINT=http://192.168.0.XXX:8333 \ +docker compose -f docker/examples/compose.local.yml up --build +# Repository convenience command: pnpm compose:local +``` + + + + +Replace `192.168.0.XXX` with your Docker host's LAN IP and allow inbound TCP ports `3003` and +`8333` through its firewall. + +:::info Internal full-stack endpoint +The full and local-build compute workers continue using `http://seaweedfs:8333` internally. +`S3_ENDPOINT` configures the app endpoint and browser-facing presigned URLs. +::: + +## Configuration + +The examples use local-only default credentials. Override existing `${VARIABLE}` values through +your shell environment before using them beyond local development. + +:::warning Protect public deployments +Replace the default `AUTH_SECRET`, PostgreSQL credentials, S3 credentials, and compute-worker +token before exposing a stack outside your trusted local network. +::: + +For the complete configuration reference, see +[Environment Variables](../reference/environment-variables). See [Database](../configure/database) +for PostgreSQL and SQLite behavior. diff --git a/docs-site/docs/deploy/local-development.md b/docs-site/docs/deploy/local-development.md index 6098225..7d81811 100644 --- a/docs-site/docs/deploy/local-development.md +++ b/docs-site/docs/deploy/local-development.md @@ -159,45 +159,10 @@ If you need mirrors or pinned artifact locations, set `WHISPER_MODEL_BASE_URL` i -
-External compute worker dev stack (optional) - -Use this only when you intentionally run compute-worker as a separate service. -Default local flow does not need `compute-worker/.env`; embedded worker startup reads root `.env`. -Full worker deployment details are in [Compute Worker (NATS JetStream)](./compute-worker). - -Start only NATS + compute-worker via compose watch: - -```bash -docker compose --env-file compute-worker/.env -f compute-worker/docker-compose.yml up --watch -# or: pnpm compute:dev:compose -``` - -`compute-worker/.env.example` contains a starter config for standalone worker service deployments. - -Run the main app separately on the host: - -```bash -pnpm dev -``` - -For app -> external worker routing, set in root `.env`: - -```env -COMPUTE_WORKER_URL=http://localhost:8081 -COMPUTE_WORKER_TOKEN= -``` - -Ownership in external worker mode: -- root `.env`: app routing/auth (`COMPUTE_WORKER_URL`, `COMPUTE_WORKER_TOKEN`) plus optional shared timeout/stale/retry overrides such as `COMPUTE_PDF_JOB_ATTEMPTS` -- `compute-worker/.env*` (or worker platform env): worker runtime variables (`NATS_*`, `S3_*`, model base URLs, worker tuning) - -For embedded worker startup (`COMPUTE_WORKER_URL` unset), worker tuning values such as `COMPUTE_PDF_JOB_ATTEMPTS` must be set in the root `.env` because `compute-worker/.env*` is ignored in that mode. - -Worker mode requires worker-reachable shared object storage (S3-compatible endpoint). -For external worker mode, object storage must be shared/reachable by both app and worker services. - -
+:::tip Docker Compose +To run OpenReader and Kokoro-FastAPI with Docker Compose, including slim, full, and local-build +options, see [Docker Compose](./docker-compose). +::: ## Steps diff --git a/docs-site/sidebars.ts b/docs-site/sidebars.ts index 63041f9..45597dc 100644 --- a/docs-site/sidebars.ts +++ b/docs-site/sidebars.ts @@ -55,7 +55,12 @@ const sidebars: SidebarsConfig = { { type: 'category', label: '🚀 Deploy', - items: ['deploy/local-development', 'deploy/compute-worker', 'deploy/vercel-deployment'], + items: [ + 'deploy/local-development', + 'deploy/docker-compose', + 'deploy/compute-worker', + 'deploy/vercel-deployment', + ], }, { type: 'category', diff --git a/package.json b/package.json index 25d915d..37ad06a 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "test:e2e": "playwright test", "test:unit": "vitest run", "test:compute": "vitest run --project compute-worker", - "compute:openapi:generate": "pnpm --dir compute-worker openapi:generate && openapi-typescript compute-worker/openapi.json -o src/lib/server/compute-worker/generated.ts", - "compute:openapi:check": "pnpm compute:openapi:generate && git diff --exit-code -- compute-worker/openapi.json src/lib/server/compute-worker/generated.ts", + "compose": "docker compose -f docker/examples/compose.yml up", + "compose:full": "docker compose -f docker/examples/compose.full.yml up", + "compose:local": "docker compose -f docker/examples/compose.local.yml up --build", "migrate": "node drizzle/scripts/migrate.mjs", "migrate-fs": "node scripts/migrate-fs-v2.mjs", "migrate-fs:dry-run": "node scripts/migrate-fs-v2.mjs --dry-run true", @@ -28,9 +29,10 @@ "docs:serve": "pnpm --dir docs-site serve", "docs:version": "pnpm --dir docs-site docusaurus docs:version", "docs:clear": "pnpm --dir docs-site clear", - "compute:worker:dev": "pnpm --filter @openreader/compute-worker dev", - "compute:worker:start": "pnpm --filter @openreader/compute-worker start", - "compute:dev:compose": "docker compose --env-file compute-worker/.env -f compute-worker/docker-compose.yml up --build", + "compute:dev": "pnpm --filter @openreader/compute-worker dev", + "compute:start": "pnpm --filter @openreader/compute-worker start", + "compute:openapi:generate": "pnpm --dir compute-worker openapi:generate && openapi-typescript compute-worker/openapi.json -o src/lib/server/compute-worker/generated.ts", + "compute:openapi:check": "pnpm compute:openapi:generate && git diff --exit-code -- compute-worker/openapi.json src/lib/server/compute-worker/generated.ts", "lint:route-errors": "node scripts/check-route-error-responses.mjs" }, "dependencies": {