openpencil/Dockerfile.web-vue
Vitali sharp8n 90baee6219 fix(docker): build the web image on the current workspace
The web image silently stopped building, so production kept serving the previous
openpencil build while the deploy job still reported success (deploy-images.sh
treats this image as optional):

- Copy every workspace manifest (COPY --parents packages/*/package.json
  tools/**/package.json) instead of a hand-kept list. Upstream added
  packages/design-jsx, packages/emit and tools/**, so the old list made
  `bun install --frozen-lockfile` fail with "Workspace not found".
- Build the SPA with Node, not bun: Vite 8 fails loading vite.config.ts under
  bun-in-Docker (vite-module-runner import-meta-resolve). bun still runs the
  lockfile install and build:packages; recreate the bunx symlink for the
  package build scripts.

Verified: docker build -f Dockerfile.web-vue succeeds and the image serves the
merged build.
2026-10-09 00:07:04 +03:00

87 lines
3.9 KiB
Docker

# syntax=docker/dockerfile:1
#
# Dockerfile.web-vue — container for the VUE editor (upstream master, v0.15.x) + MCP.
#
# Replaces the retired Dockerfile.web-rust (Rust web host, main branch). This
# image serves the static SPA build (the same `dist/` artifact that Cloudflare
# Pages deploys for app.openpencil.dev) on :3100 AND the @open-pencil/mcp
# Streamable-HTTP server on :7600, both from one container via the entrypoint.
# w4c-chatapi talks MCP to http://openpencil:7600/mcp (OpenPencilMcp__HttpEndpoint).
#
# Build:
# docker build -f Dockerfile.web-vue -t w4c-openpencil:latest .
#
# Run:
# docker run -p 3100:3100 -p 7600:7600 \
# -e OPENPENCIL_MCP_AUTH_TOKEN=... \
# -e OPENPENCIL_MCP_CORS_ORIGIN=https://openpencil.wiz4chat.com \
# -v openpencil_data:/data \
# w4c-openpencil:latest
# ── Stage 1: builder — bun for install/package build, Node for Vite ──────────
#
# The SPA is built with Node, not bun. Vite 8 bundles vite.config.ts through its
# module runner, and that step fails under bun-in-Docker regardless of the bun
# version (`Cannot find package 'vite-module-runner:import-meta-resolve'` on
# 1.4.2, a bare `ResolveMessage {}` on 1.3.14), while the same tree builds under
# Node 22 here and under bun on the host. bun still owns the lockfile install
# and `build:packages` (which runs TypeScript through bun).
FROM oven/bun:1 AS bun
FROM node:22-bookworm AS builder
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
# Package build scripts call `bunx tsdown`; the official image provides bunx only
# as a symlink to bun, so recreate it (argv[0] === "bunx" makes bun run `bun x`).
RUN ln -s /usr/local/bin/bun /usr/local/bin/bunx
WORKDIR /app
# Lockfile + manifests first for layer caching. `COPY --parents` mirrors the whole
# workspace tree without listing each workspace by hand — the explicit list went
# stale as upstream added workspaces (packages/design-jsx, packages/emit, tools/**),
# and a missing manifest makes `bun install --frozen-lockfile` fail with
# "Workspace not found". That failure is silent in production: deploy-images.sh
# treats this image as optional, so the deploy job still succeeds while the
# openpencil container keeps serving the previous build.
COPY package.json bun.lock bunfig.toml ./
COPY --parents packages/*/package.json tools/**/package.json ./
RUN bun install --frozen-lockfile
# Full source (excludes heavy local build outputs via .dockerignore).
COPY . .
# Build the workspace packages (bun) and the SPA (Node — see the stage note
# above). Deliberately NOT `bun run build`: that also runs the repo's oxlint
# quality gate (`lint:structure` + type-aware oxlint over ~2k files). Linting is
# a CI/`bun run check` concern, not an image build step — it added ~7 s and
# spammed the deploy log with the fork's pre-existing `max-lines` warnings.
RUN bun run build:packages && node node_modules/vite/bin/vite.js build
# Prune to production dependencies so the runtime layer is lean. bun keeps
# workspace symlinks; the MCP server's deps (hono, @modelcontextprotocol/sdk,
# ws, zod + @open-pencil/core) are all in the prod graph.
RUN bun install --production --frozen-lockfile
# ── Stage 2: runtime — static SPA (:3100) + MCP (:7600) ─────────────────────
FROM oven/bun:1-slim AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/bun.lock ./bun.lock
COPY --from=builder /app/bunfig.toml ./bunfig.toml
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/packages ./packages
COPY --from=builder /app/docker/static-server.mjs ./static-server.mjs
COPY --from=builder /app/docker/entrypoint.sh /entrypoint.sh
# Writable document root for MCP file-scoped tools (open_file / new_document /
# save_file). Keep on a volume so documents survive container restarts.
ENV OPENPENCIL_MCP_ROOT=/data
EXPOSE 3100 7600
ENTRYPOINT ["/entrypoint.sh"]