diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3247aed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,60 @@ +# Adapted from novelpad-desktop Dockerfile @ 62c56b87 — same multi-stage +# shape (base → deps → builder → prod-deps → runner), trimmed to this repo: +# no native modules (pg/pdfjs/fast-xml-parser are pure JS), so no +# python3/make/g++ layer; no pm2 (one process per container, compose picks +# the command); default CMD is the DBOS worker, the review app overrides. + +# Stage 1: Base image with yarn + turbo +FROM node:22-slim AS base +WORKDIR /app +RUN corepack enable && corepack prepare yarn@4.5.0 && npm i -g turbo + +# Stage 2: Install dependencies (rebuilds only when manifests change) +FROM base AS deps +WORKDIR /app +COPY package.json yarn.lock .yarnrc.yml ./ +COPY packages/config/package.json packages/config/ +COPY packages/outreach-core/package.json packages/outreach-core/ +COPY packages/outreach-ai/package.json packages/outreach-ai/ +COPY apps/outreach-worker/package.json apps/outreach-worker/ +COPY apps/outreach-review/package.json apps/outreach-review/ +RUN --mount=type=cache,target=/root/.yarn/berry/cache \ + yarn install --immutable + +# Stage 3: Build everything +FROM base AS builder +WORKDIR /app +COPY --from=deps /app ./ +COPY . . +RUN --mount=type=cache,target=/app/.turbo \ + turbo build --filter=@novelpad/outreach-worker... --filter=@novelpad/outreach-review... + +# Stage 4: Production dependencies only +FROM deps AS prod-deps +RUN yarn workspaces focus --production @novelpad/outreach-worker @novelpad/outreach-review && yarn cache clean + +# Stage 5: Final production image (no build tools) +FROM node:22-slim AS runner +WORKDIR /app +ENV NODE_ENV=production +RUN corepack enable && corepack prepare yarn@4.5.0 + +COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules +COPY --from=prod-deps --chown=node:node /app/package.json ./package.json +COPY --from=prod-deps --chown=node:node /app/yarn.lock ./yarn.lock +COPY --from=prod-deps --chown=node:node /app/.yarnrc.yml ./.yarnrc.yml + +COPY --chown=node:node packages/config/package.json packages/config/ +COPY --chown=node:node packages/outreach-core/package.json packages/outreach-core/ +COPY --chown=node:node packages/outreach-ai/package.json packages/outreach-ai/ +COPY --chown=node:node apps/outreach-worker/package.json apps/outreach-worker/ +COPY --chown=node:node apps/outreach-review/package.json apps/outreach-review/ + +COPY --from=builder --chown=node:node /app/packages/outreach-core/dist ./packages/outreach-core/dist +COPY --from=builder --chown=node:node /app/packages/outreach-core/drizzle ./packages/outreach-core/drizzle +COPY --from=builder --chown=node:node /app/packages/outreach-ai/dist ./packages/outreach-ai/dist +COPY --from=builder --chown=node:node /app/apps/outreach-worker/build ./apps/outreach-worker/build +COPY --from=builder --chown=node:node /app/apps/outreach-review/build ./apps/outreach-review/build + +USER node +CMD ["node", "apps/outreach-worker/build/main.js"] diff --git a/README.md b/README.md index 28600e3..04f133f 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,16 @@ yarn test ``` Migrations: `yarn create:migration ` inside `packages/outreach-core` (server-only — no client DB in this repo). + +## Local infrastructure + +`docker-compose.yml` mirrors novelpad-desktop's compose conventions (pgvector image, healthcheck-gated startup), trimmed to this repo: Postgres 16 + pgvector on host port **54341** (novelpad's stacks use 54321/54331 — all three run side by side), plus `worker` and `review` services built from the root multi-stage `Dockerfile` (same image, different command — the novelpad website/auth pattern). + +``` +docker compose up -d postgres # just the database +DATABASE_URL=postgresql://postgres:password@localhost:54341/helmdocs_outreach \ + yarn workspace @novelpad/outreach-core migrate +docker compose up -d # full stack (worker + review UI on :3100) +``` + +Migrations always run from the host — drizzle-kit is a devDependency and deliberately absent from the production image. The DBOS system schema initializes itself into the same database on worker boot (the `dbos` schema; ignore the misleading `*_dbos_sys` URL in DBOS's boot log — the shared pool override wins). diff --git a/apps/outreach-worker/.env.example b/apps/outreach-worker/.env.example index 8568e20..8700a19 100644 --- a/apps/outreach-worker/.env.example +++ b/apps/outreach-worker/.env.example @@ -1,7 +1,8 @@ # Postgres connection string shared by the DBOS system schema, the pg Pool, # and the Drizzle client over the outreach schema. Required — main.ts throws # on boot if this is unset. -DATABASE_URL=postgres://postgres:postgres@localhost:5432/helmdocs_outreach +# For the dockerized DB (docker compose up -d postgres) use port 54341: +DATABASE_URL=postgresql://postgres:password@localhost:54341/helmdocs_outreach # Path to a GCP service account key JSON used by ingestion/scoring steps that # call Google-hosted APIs (e.g. @google/genai subscoring, Drive-backed org diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c97d691 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,69 @@ +# Mirrors novelpad-desktop's docker-compose.yml conventions (pgvector/pgvector +# image, healthcheck-gated depends_on, named volumes) trimmed to this repo's +# needs: no Electric (no client sync), no Redis. Host port 54341 avoids +# novelpad-local's 54321 and website-compose's 54331 so all three stacks can +# run side by side. +# +# Migrations run from the host (drizzle-kit is a devDependency, deliberately +# absent from the production image): +# DATABASE_URL=postgresql://postgres:password@localhost:54341/helmdocs_outreach \ +# yarn workspace @novelpad/outreach-core migrate +name: 'grant-outreach-local' + +services: + worker: + build: + context: . + dockerfile: Dockerfile + environment: + NODE_ENV: production + DATABASE_URL: postgresql://postgres:password@postgres:5432/helmdocs_outreach + # Uncomment (and mount below) once Phase 2 agents call Vertex: + # GCP_SERVICE_ACCOUNT_KEY_PATH: /app/.gcp/service-account.json + # NHDOJ_REGISTRY_PDF_URL: https://www.doj.nh.gov/...pdf + # volumes: + # - ./.gcp/service-account.json:/app/.gcp/service-account.json:ro + depends_on: + postgres: + condition: service_healthy + + review: + build: + context: . + dockerfile: Dockerfile + command: ['yarn', 'workspace', '@novelpad/outreach-review', 'start'] + ports: + - '3100:3100' + environment: + NODE_ENV: production + PORT: 3100 + DATABASE_URL: postgresql://postgres:password@postgres:5432/helmdocs_outreach + depends_on: + postgres: + condition: service_healthy + + postgres: + image: pgvector/pgvector:pg16 + environment: + POSTGRES_DB: helmdocs_outreach + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - '54341:5432' + volumes: + - postgres_data:/var/lib/postgresql/data + tmpfs: + - /tmp + command: + - -c + - listen_addresses=* + - -c + - log_min_messages=notice + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: