feat(infra): dockerized Postgres+pgvector and app image, mirroring novelpad compose conventions
docker-compose.yml: pgvector/pgvector:pg16 on host 54341 (side-by-side
with novelpad's 54321/54331), healthcheck-gated worker + review services
from one multi-stage Dockerfile (same image, different command — the
website/auth pattern). No Electric/Redis: nothing client-synced here.
Dockerfile adapted from novelpad-desktop @ 62c56b87, trimmed: no native
modules so no build-tools layer, no pm2. Default CMD = DBOS worker.
Verified end to end: migration applied against the container (vector
0.8.2, 6 tables, 2 HNSW indexes), containerized worker boots, DBOS
system schema lands in helmdocs_outreach ('dbos' schema — the
*_dbos_sys URL in DBOS's boot log is cosmetic; the shared pool wins).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
60
Dockerfile
Normal file
60
Dockerfile
Normal file
@@ -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"]
|
||||||
13
README.md
13
README.md
@@ -38,3 +38,16 @@ yarn test
|
|||||||
```
|
```
|
||||||
|
|
||||||
Migrations: `yarn create:migration <name>` inside `packages/outreach-core` (server-only — no client DB in this repo).
|
Migrations: `yarn create:migration <name>` 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).
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Postgres connection string shared by the DBOS system schema, the pg Pool,
|
# Postgres connection string shared by the DBOS system schema, the pg Pool,
|
||||||
# and the Drizzle client over the outreach schema. Required — main.ts throws
|
# and the Drizzle client over the outreach schema. Required — main.ts throws
|
||||||
# on boot if this is unset.
|
# 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
|
# 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
|
# call Google-hosted APIs (e.g. @google/genai subscoring, Drive-backed org
|
||||||
|
|||||||
69
docker-compose.yml
Normal file
69
docker-compose.yml
Normal file
@@ -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:
|
||||||
Reference in New Issue
Block a user