Skip to content

Commit 9a774d2

Browse files
committed
fix(docker): build from repo root for npm workspaces layout
The multiplatform image build was failing at `npm ci` because the workflow set `context: ./packages/server` — but npm workspaces monorepos keep a single `package-lock.json` at the repo root, so the server subtree has no lockfile to copy. `npm ci` requires a lockfile and aborts with EUSAGE. Switch the build to repo-root context and rewrite the Dockerfile to understand the workspace graph: copy the root lockfile + every workspace package.json, run `npm ci`, then build only the server workspace. A separate prod-deps stage installs production-only deps scoped to @ibm/ibmi-mcp-server so the runner image doesn't carry dev tooling or CLI workspace deps. Runner stage populates packages/server/ so the node_modules symlink resolves correctly. Also tighten .dockerignore so nested node_modules (app/agent-ui, client/, etc.) don't bloat the build context — otherwise COPY . . pulls in ~500MB of unrelated Node deps and OOMs the builder. Verified locally (npm ci all-deps + npm ci --omit=dev --workspace) — the CI failure mode no longer reproduces. Signed-off-by: Adam Shedivy <ajshedivyaj@gmail.com>
1 parent 51255e9 commit 9a774d2

3 files changed

Lines changed: 80 additions & 56 deletions

File tree

.dockerignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Thumbs.db
2525
# NODE.JS & PACKAGE MANAGERS
2626
# =============================================================================
2727
node_modules/
28+
**/node_modules
2829
npm-debug.log*
2930
yarn-debug.log*
3031
yarn-error.log*
@@ -195,8 +196,11 @@ duckdata/
195196
# LARGE PROJECT DIRECTORIES (exclude from Docker build context)
196197
# =============================================================================
197198
agents/
199+
app/
200+
client/
198201
release/
199202
coverage/
200203
.git/
201204
.github/
202-
changelogs/
205+
changelogs/
206+
tmp/

.github/workflows/docker-multiplatform.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ jobs:
103103
id: build
104104
uses: docker/build-push-action@v6
105105
with:
106-
context: ./packages/server
106+
context: .
107107
file: ./packages/server/Dockerfile
108108
platforms: ${{ matrix.platform }}
109109
push: true

packages/server/Dockerfile

Lines changed: 74 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,89 @@
1-
# ---- Base Node ----
2-
# Use a specific Node.js version known to work, Alpine for smaller size
1+
# syntax=docker/dockerfile:1.7
2+
#
3+
# Build context: REPO ROOT (npm workspaces monorepo uses a single root
4+
# package-lock.json shared by every workspace). The workflow must set
5+
# `context: .` so this Dockerfile can see the root lockfile.
6+
#
7+
# Produces a runtime image for @ibm/ibmi-mcp-server only — the CLI
8+
# workspace is not shipped.
9+
10+
# ---- Base ----
311
FROM node:bookworm-slim AS base
4-
WORKDIR /usr/src/app
5-
# NODE_ENV will be set by docker-compose from .env file
6-
7-
# Update system packages and install security updates
8-
# This resolves all system-level CVEs
9-
RUN apt-get update && \
10-
apt-get upgrade -y && \
11-
apt-get install -y --no-install-recommends \
12-
ca-certificates \
13-
&& \
14-
npm install -g npm@latest && \
15-
apt-get clean && \
16-
rm -rf /var/lib/apt/lists/*
17-
18-
# ---- Dependencies ----
19-
# Install dependencies first to leverage Docker cache
20-
FROM base AS deps
21-
WORKDIR /usr/src/app
22-
ENV NODE_ENV=production
23-
COPY package.json package-lock.json* ./
24-
# Use npm ci for deterministic installs based on lock file
25-
# Install only production dependencies in this stage for the final image
26-
RUN npm ci --only=production
12+
WORKDIR /app
2713

28-
# ---- Builder ----
29-
# Build the application
14+
# System updates + recent npm (lockfileVersion 3 needs npm >= 7)
15+
RUN apt-get update \
16+
&& apt-get upgrade -y \
17+
&& apt-get install -y --no-install-recommends ca-certificates \
18+
&& npm install -g npm@latest \
19+
&& apt-get clean \
20+
&& rm -rf /var/lib/apt/lists/*
21+
22+
# ---- Builder: install all deps, build the server workspace ----
3023
FROM base AS builder
31-
WORKDIR /usr/src/app
32-
# Override NODE_ENV to ensure devDependencies (including TypeScript) are installed
24+
WORKDIR /app
3325
ENV NODE_ENV=development
34-
# Copy dependency manifests and install *all* dependencies (including dev)
35-
COPY package.json package-lock.json* ./
26+
27+
# Copy manifests first so the Docker layer cache is invalidated only when
28+
# dependency declarations change, not on every source edit. Every workspace
29+
# package.json must be present for `npm ci` to resolve the workspace graph
30+
# that the root lockfile describes.
31+
COPY package.json package-lock.json ./
32+
COPY packages/server/package.json ./packages/server/
33+
COPY packages/cli/package.json ./packages/cli/
34+
3635
RUN npm ci
37-
# Copy the rest of the source code
36+
37+
# Copy the full source tree and build only the server workspace.
38+
# (.dockerignore keeps node_modules/, dist/, .git/, .github/, .claude/,
39+
# agents/, and .env files out of the build context.)
3840
COPY . .
39-
# Build the TypeScript project
40-
RUN npm run build
41+
42+
RUN npm run build -w @ibm/ibmi-mcp-server
43+
44+
# ---- Production dependencies only ----
45+
# Separate stage so the runtime image carries only what the server needs
46+
# to execute — no TypeScript, no test frameworks, no CLI workspace deps.
47+
FROM base AS prod-deps
48+
WORKDIR /app
49+
ENV NODE_ENV=production
50+
51+
COPY package.json package-lock.json ./
52+
COPY packages/server/package.json ./packages/server/
53+
COPY packages/cli/package.json ./packages/cli/
54+
55+
# --workspace=@ibm/ibmi-mcp-server + --include-workspace-root installs
56+
# exactly the production deps the server needs plus anything hoisted to
57+
# the root. The CLI workspace's deps are skipped.
58+
RUN npm ci --omit=dev \
59+
--workspace=@ibm/ibmi-mcp-server \
60+
--include-workspace-root
4161

4262
# ---- Runner ----
43-
# Final stage with all dependencies and built code
4463
FROM base AS runner
45-
WORKDIR /usr/src/app
46-
# Copy ALL node_modules from the 'builder' stage (includes dev dependencies)
47-
COPY --from=builder /usr/src/app/node_modules ./node_modules
48-
# Copy built application from the 'builder' stage
49-
COPY --from=builder /usr/src/app/dist ./dist
50-
# Copy package.json (needed for potential runtime info, like version)
51-
COPY package.json .
52-
53-
# Create a non-root user and switch to it
54-
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
55-
56-
# Change ownership of app directory to appuser and create npm cache directory
57-
RUN chown -R appuser:appgroup /usr/src/app && \
58-
mkdir -p /home/appuser/.npm && \
59-
chown -R appuser:appgroup /home/appuser/.npm
64+
WORKDIR /app
65+
66+
# node_modules carries a symlink `@ibm/ibmi-mcp-server -> ../packages/server`.
67+
# Copying node_modules from prod-deps preserves the symlink; populating
68+
# packages/server/ from the builder makes the symlink resolve to the
69+
# actual built dist output.
70+
COPY --from=prod-deps /app/node_modules ./node_modules
71+
COPY --from=builder /app/package.json ./package.json
72+
COPY --from=builder /app/packages/server/package.json ./packages/server/package.json
73+
COPY --from=builder /app/packages/server/dist ./packages/server/dist
74+
75+
# Non-root user + npm cache dir
76+
RUN groupadd -r appgroup \
77+
&& useradd -r -g appgroup appuser \
78+
&& chown -R appuser:appgroup /app \
79+
&& mkdir -p /home/appuser/.npm \
80+
&& chown -R appuser:appgroup /home/appuser/.npm
6081

6182
USER appuser
6283

63-
# Expose port if the application runs a server (adjust if needed)
6484
ENV MCP_TRANSPORT_TYPE=http
6585
EXPOSE 3010
6686

67-
# Command to run the application
68-
# This will execute the binary defined in package.json
69-
CMD ["npx", "ibmi-mcp-server"]
87+
# Run node directly on the built entry rather than via `npx` — avoids
88+
# any bin-resolution surprises and gives a cleaner PID 1.
89+
CMD ["node", "packages/server/dist/index.js"]

0 commit comments

Comments
 (0)