diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0b2d5db --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +*/node_modules +dist +*/dist +.git +*.md +.env* +logs.txt +*.session.sql diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..eccf7d8 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +inject-workspace-packages=true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b1f9837 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# --- Stage 1: base (install deps) --- +FROM node:24-alpine AS base +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc ./ +COPY packages/common/package.json packages/common/ +COPY apps/backend/package.json apps/backend/ +COPY apps/webpage/package.json apps/webpage/ +RUN pnpm install --frozen-lockfile + +# --- Stage 2: build-common --- +FROM base AS build-common +COPY packages/common/ packages/common/ +RUN pnpm --filter @cuttlekit/common build + +# --- Stage 3: build-backend --- +FROM build-common AS build-backend +COPY apps/backend/ apps/backend/ +RUN pnpm --filter @cuttlekit/backend build +RUN pnpm deploy --filter @cuttlekit/backend --prod /app/deployed + +# --- Stage 4: build-webpage --- +FROM build-common AS build-webpage +ARG VITE_API_BASE="http://localhost:34512" +ENV VITE_API_BASE=${VITE_API_BASE} +COPY apps/webpage/ apps/webpage/ +RUN pnpm --filter @cuttlekit/webpage build + +# --- Stage 5: backend runtime --- +FROM node:24-alpine AS backend +RUN apk add --no-cache tini +WORKDIR /app +COPY --from=build-backend /app/deployed/node_modules/ ./node_modules/ +COPY --from=build-backend /app/apps/backend/dist/ ./dist/ +COPY config.toml ./ +COPY drizzle/ ./drizzle/ +RUN mkdir -p /app/data && chown node:node /app/data +ENV DATABASE_URL=file:/app/data/memory.db +USER node +EXPOSE 34512 +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["node", "dist/index.mjs"] + +# --- Stage 6: webpage runtime --- +FROM node:24-alpine AS webpage +RUN npm i -g serve +WORKDIR /app +COPY --from=build-webpage /app/apps/webpage/dist/ ./dist/ +USER node +EXPOSE 34513 +CMD ["serve", "-s", "dist", "-l", "34513"] diff --git a/README.md b/README.md index 9a3e230..95a6ed3 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,20 @@ pnpm run dev:backend:no-env Then open http://localhost:5173 🚀 +### Docker + +Make sure you have a `config.toml` (see step 3) and your env vars ready, then: + +```bash +# With .env file +docker compose up --build + +# With 1Password CLI (or similar secret injectors) +op run --env-file=.env -- docker compose up --build +``` + +Open http://localhost:34513 + ## Current Constraints We're actively working on these: diff --git a/apps/backend/tsdown.config.ts b/apps/backend/tsdown.config.ts new file mode 100644 index 0000000..5f18938 --- /dev/null +++ b/apps/backend/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: "esm", + // By default tsdown externalizes all node_modules deps. + // Force-bundle workspace packages and their transitive pure-JS deps + // so the output is self-contained for Docker deployment. + noExternal: [/@cuttlekit\//, "effect", /^@effect\//, "drizzle-orm"], +}); diff --git a/apps/webpage/src/main.ts b/apps/webpage/src/main.ts index d80ff50..8de1267 100644 --- a/apps/webpage/src/main.ts +++ b/apps/webpage/src/main.ts @@ -3,7 +3,7 @@ import { loadFontsFromHTML } from "./fonts"; import { loadIconsFromHTML } from "./icons"; import type { Action, Patch, StreamEventWithOffset } from "@cuttlekit/common/client"; -const API_BASE = "http://localhost:34512"; +const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:34512"; const STORAGE_KEY = "generative-ui-stream"; const MODEL_STORAGE_KEY = "generative-ui-model"; diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..e336408 --- /dev/null +++ b/compose.yml @@ -0,0 +1,25 @@ +services: + backend: + build: + context: . + target: backend + ports: + - "34512:34512" + environment: + - GOOGLE_API_KEY=${GOOGLE_API_KEY:-} + - GROQ_API_KEY=${GROQ_API_KEY:-} + - INCEPTION_API_KEY=${INCEPTION_API_KEY:-} + - DENO_API_KEY=${DENO_API_KEY:-} + - LINEAR_API_KEY=${LINEAR_API_KEY:-} + - NOTION_API_KEY=${NOTION_API_KEY:-} + restart: unless-stopped + + webpage: + build: + context: . + target: webpage + ports: + - "34513:34513" + depends_on: + - backend + restart: unless-stopped diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f92332..893c024 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false + injectWorkspacePackages: true overrides: vite: npm:rolldown-vite@7.2.5 @@ -135,6 +136,8 @@ importers: packages/common: {} + packages/cuttlekit: {} + packages: '@ai-sdk/gateway@3.0.55':