-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathDockerfile
More file actions
276 lines (242 loc) · 15 KB
/
Copy pathDockerfile
File metadata and controls
276 lines (242 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# Stage 1: Build and bundle the TypeScript application
# All adapters build; runtime availability is governed by the stage-2
# DEBUG_MCP_DISABLE_LANGUAGES value (issue #328).
ARG DISABLE_LANGUAGES=
FROM node:26-slim@sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a AS builder
ARG DISABLE_LANGUAGES
ENV DEBUG_MCP_DISABLE_LANGUAGES=${DISABLE_LANGUAGES}
# Install pnpm via corepack (version 10 to match local development).
# node:26-slim no longer bundles corepack, so install it explicitly (pinned,
# matching the rest of this Dockerfile's exact-version pins) before enabling;
# the activated pnpm version is still integrity-checked against the spec.
RUN npm install -g corepack@0.35.0 && corepack enable && corepack prepare pnpm@10.33.0 --activate
# Set application directory
WORKDIR /app
# Add container marker
ENV MCP_CONTAINER=true
# Cache busting argument - changes this will invalidate all subsequent layers
ARG CACHEBUST=1
# 1) Copy ONLY manifests for dependency install (preserves cache)
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/shared/package.json ./packages/shared/package.json
COPY packages/codelldb-common/package.json ./packages/codelldb-common/package.json
COPY packages/adapter-mock/package.json ./packages/adapter-mock/package.json
COPY packages/adapter-python/package.json ./packages/adapter-python/package.json
COPY packages/adapter-javascript/package.json ./packages/adapter-javascript/package.json
COPY packages/adapter-rust/package.json ./packages/adapter-rust/package.json
COPY packages/adapter-go/package.json ./packages/adapter-go/package.json
COPY packages/adapter-java/package.json ./packages/adapter-java/package.json
COPY packages/adapter-ruby/package.json ./packages/adapter-ruby/package.json
COPY packages/adapter-dotnet/package.json ./packages/adapter-dotnet/package.json
COPY packages/adapter-cpp/package.json ./packages/adapter-cpp/package.json
# 2) Install dependencies with workspace support using the lockfile
# If lockfile is stale, this will fail (good signal to refresh it locally).
# Copy all package sources to allow pnpm to resolve workspace:* links
COPY packages ./packages
# Remove any existing dist folders and tsbuildinfo artifacts from packages to prevent stale
# build outputs (and their cached path maps) from polluting the Docker build.
RUN set -eux; \
for pkg in ./packages/*; do \
[ -d "$pkg" ] || continue; \
rm -rf "$pkg/dist" "$pkg/tsconfig.tsbuildinfo"; \
done
RUN pnpm --version && pnpm install --frozen-lockfile --ignore-scripts
# 3) Copy the rest of the sources and build configs
COPY tsconfig*.json ./
COPY packages/shared/tsconfig*.json ./packages/shared/
COPY packages/codelldb-common/tsconfig*.json ./packages/codelldb-common/
COPY packages/adapter-mock/tsconfig*.json ./packages/adapter-mock/
COPY packages/adapter-python/tsconfig*.json ./packages/adapter-python/
COPY packages/adapter-javascript/tsconfig*.json ./packages/adapter-javascript/
COPY packages/adapter-rust/tsconfig*.json ./packages/adapter-rust/
COPY packages/adapter-go/tsconfig*.json ./packages/adapter-go/
COPY packages/adapter-java/tsconfig*.json ./packages/adapter-java/
COPY packages/adapter-ruby/tsconfig*.json ./packages/adapter-ruby/
COPY packages/adapter-dotnet/tsconfig*.json ./packages/adapter-dotnet/
COPY packages/adapter-cpp/tsconfig*.json ./packages/adapter-cpp/
COPY src ./src
COPY scripts ./scripts/
# 4) Vendor the CodeLLDB engine for this image's architecture (rust + cpp adapters).
# This MUST be explicit: the root postinstall that vendors on dev machines is
# skipped by --ignore-scripts, and codelldb-common's package build is tsc-only —
# without this step a fresh CI context ships an image with no CodeLLDB (#387;
# the committed vendor/.gitkeep kept the later cp -r from failing, so v0.24.0
# shipped silently broken). Implemented in plain shell (curl + sha256sum +
# unzip) because the node vendor script dies mid-extraction with exit 0 under
# buildkit (#389); the download is verified against the pinned SHA-256 digests
# in packages/codelldb-common/vendor-manifest.json. A "current" symlink gives
# the runtime stage an architecture-independent CODELLDB_PATH. If the build
# context already carries a vendored engine (local dev builds), it is reused.
ARG TARGETARCH
RUN set -eux; \
case "${TARGETARCH:-amd64}" in arm64) CODELLDB_ARCH=linux-arm64;; *) CODELLDB_ARCH=linux-x64;; esac; \
DEST="/app/packages/codelldb-common/vendor/codelldb/${CODELLDB_ARCH}"; \
if [ ! -x "$DEST/adapter/codelldb" ]; then \
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates unzip && rm -rf /var/lib/apt/lists/*; \
MANIFEST=/app/packages/codelldb-common/vendor-manifest.json; \
CODELLDB_VERSION="$(node -p "require('$MANIFEST').codelldb.version")"; \
EXPECTED_SHA="$(node -p "require('$MANIFEST').codelldb.assets['codelldb-${CODELLDB_ARCH}.vsix']")"; \
curl -fsSL --retry 3 -o /tmp/codelldb.vsix "https://github.com/vadimcn/codelldb/releases/download/v${CODELLDB_VERSION}/codelldb-${CODELLDB_ARCH}.vsix"; \
echo "${EXPECTED_SHA} /tmp/codelldb.vsix" | sha256sum -c -; \
unzip -q /tmp/codelldb.vsix -d /tmp/codelldb-extract; \
rm -rf "$DEST"; mkdir -p "$DEST"; \
cp -r /tmp/codelldb-extract/extension/adapter "$DEST/adapter"; \
cp -r /tmp/codelldb-extract/extension/lldb "$DEST/lldb"; \
if [ -d /tmp/codelldb-extract/extension/lang_support ]; then cp -r /tmp/codelldb-extract/extension/lang_support "$DEST/lang_support"; fi; \
chmod 755 "$DEST/adapter/codelldb"; \
printf '{\n "version": "%s",\n "platform": "%s"\n}\n' "$CODELLDB_VERSION" "$CODELLDB_ARCH" > "$DEST/version.json"; \
rm -rf /tmp/codelldb.vsix /tmp/codelldb-extract; \
fi; \
test -x "$DEST/adapter/codelldb"; \
ln -sfn "$CODELLDB_ARCH" /app/packages/codelldb-common/vendor/codelldb/current
# 5) Build workspace packages and main project (root build runs build:packages); then bundle.
# The node vendor script runs via prebuild -> vendor:adapters; without this env it
# would default to all five platforms and re-download the win32/darwin payloads the
# .dockerignore deliberately excludes (~450 MB the Linux image never uses, and a
# needless network dependency that can fail the build). Host-only mode finds the
# shell-vendored engine above already fresh and downloads nothing.
ENV CODELLDB_VENDOR_ALL=false
RUN pnpm run build --silent
RUN node scripts/bundle.js
# Optional: quick diagnostics for bundle
RUN echo "=== Listing dist directory after bundling ===" && \
ls -la dist/ && \
echo "=== Checking for bundle.cjs ===" && \
ls -la dist/bundle.cjs || true && \
echo "=== Bundle size ===" && \
(command -v du >/dev/null 2>&1 && du -h dist/bundle.cjs) || true
# 5) Ensure adapter packages are available in node_modules
# pnpm uses symlinks that don't survive Docker COPY, so we need to replace them with actual files
RUN rm -rf /app/node_modules/@debugmcp && \
mkdir -p /app/node_modules/@debugmcp/shared && \
mkdir -p /app/node_modules/@debugmcp/adapter-mock && \
mkdir -p /app/node_modules/@debugmcp/adapter-python && \
mkdir -p /app/node_modules/@debugmcp/adapter-javascript && \
cp -r /app/packages/shared/dist /app/node_modules/@debugmcp/shared/ && \
cp /app/packages/shared/package.json /app/node_modules/@debugmcp/shared/ && \
cp -r /app/packages/adapter-mock/dist /app/node_modules/@debugmcp/adapter-mock/ && \
cp /app/packages/adapter-mock/package.json /app/node_modules/@debugmcp/adapter-mock/ && \
cp -r /app/packages/adapter-python/dist /app/node_modules/@debugmcp/adapter-python/ && \
cp /app/packages/adapter-python/package.json /app/node_modules/@debugmcp/adapter-python/ && \
cp -r /app/packages/adapter-javascript/dist /app/node_modules/@debugmcp/adapter-javascript/ && \
cp -r /app/packages/adapter-javascript/vendor /app/node_modules/@debugmcp/adapter-javascript/ && \
cp /app/packages/adapter-javascript/package.json /app/node_modules/@debugmcp/adapter-javascript/ && \
mkdir -p /app/node_modules/@debugmcp/adapter-java && \
cp -r /app/packages/adapter-java/dist /app/node_modules/@debugmcp/adapter-java/ && \
cp -r /app/packages/adapter-java/java /app/node_modules/@debugmcp/adapter-java/ && \
cp /app/packages/adapter-java/package.json /app/node_modules/@debugmcp/adapter-java/ && \
mkdir -p /app/node_modules/@debugmcp/adapter-ruby && \
cp -r /app/packages/adapter-ruby/dist /app/node_modules/@debugmcp/adapter-ruby/ && \
cp /app/packages/adapter-ruby/package.json /app/node_modules/@debugmcp/adapter-ruby/ && \
mkdir -p /app/node_modules/@debugmcp/codelldb-common && \
cp -r /app/packages/codelldb-common/dist /app/node_modules/@debugmcp/codelldb-common/ && \
cp -r /app/packages/codelldb-common/vendor /app/node_modules/@debugmcp/codelldb-common/ && \
cp /app/packages/codelldb-common/package.json /app/node_modules/@debugmcp/codelldb-common/ && \
mkdir -p /app/node_modules/@debugmcp/adapter-cpp && \
cp -r /app/packages/adapter-cpp/dist /app/node_modules/@debugmcp/adapter-cpp/ && \
cp /app/packages/adapter-cpp/package.json /app/node_modules/@debugmcp/adapter-cpp/ && \
mkdir -p /app/node_modules/@debugmcp/adapter-rust && \
cp -r /app/packages/adapter-rust/dist /app/node_modules/@debugmcp/adapter-rust/ && \
cp /app/packages/adapter-rust/package.json /app/node_modules/@debugmcp/adapter-rust/
# Rust LLDB formatter scripts (issue #441): pure-Python files shipped with
# every Rust toolchain, which CodeLLDB never bundles. The runtime image has
# no rustc, so CodeLLDB's lang_support/rust.py cannot locate them via
# `rustc --print sysroot`; instead they are vendored here and surfaced via
# CODELLDB_RUST_SYSROOT, which the rust adapter turns into the CodeLLDB
# setting lang.rust.sysroot (_adapterSettings.scriptConfig). Digest is the
# multi-arch OCI index, so amd64 and arm64 TARGETARCH builds both resolve.
# Bump alongside CodeLLDB bumps or when formatter drift is reported.
FROM rust:1.98.0-slim@sha256:cc0448b41c3b7b7fea44f5dc50eacba729a56db365b65b7bd5e8a82d5b3db078 AS rust-formatters
RUN cp -r "$(rustc --print sysroot)/lib/rustlib/etc" /rust-etc
# Stage 2: Create runtime image with full LLDB dependencies
FROM ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b
# Disabled languages: go has no attach implementation and no Delve here,
# dotnet has no netcoredbg here. Ruby is intentionally present but attach-only
# (adapter shipped, no Ruby runtime — attach connects directly to a remote
# rdbg socket, issue #331). rust and cpp are enabled with vendored CodeLLDB
# (#328) — sound for Linux-compiled binaries; host-compiled (Windows/macOS)
# binaries mounted into the container are not debuggable by container LLDB.
ENV DEBUG_MCP_DISABLE_LANGUAGES=go,dotnet
# Set application directory
WORKDIR /app
# Set container marker for runtime
ENV MCP_CONTAINER=true
# Set default workspace mount location (can be overridden at runtime)
ENV MCP_WORKSPACE_ROOT=/workspace
# Install Python, LLDB, and supporting tools (Node copied from builder)
COPY requirements/debugpy.txt /tmp/debugpy-requirements.txt
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl \
ca-certificates \
strace \
procps \
lsof \
tini \
python3 \
python3-pip \
python3-venv \
libstdc++6 \
libatomic1 \
lldb \
python3-lldb \
g++ \
openjdk-21-jdk-headless && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
pip3 install --break-system-packages --no-cache-dir --require-hashes -r /tmp/debugpy-requirements.txt && \
rm /tmp/debugpy-requirements.txt
# Copy Node runtime from builder to avoid installing system-wide Node.js
COPY --from=builder /usr/local/bin/node /usr/local/bin/node
COPY --from=builder /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -sf /usr/local/bin/node /usr/bin/node
# Copy ONLY the bundled server and proxy files (everything else is bundled)
COPY --from=builder /app/dist/bundle.cjs /app/dist/bundle.cjs
COPY --from=builder /app/dist/proxy/proxy-bootstrap.js /app/dist/proxy/proxy-bootstrap.js
COPY --from=builder /app/dist/proxy/proxy-bundle.cjs /app/dist/proxy/proxy-bundle.cjs
COPY --from=builder /app/dist/proxy/utils /app/dist/proxy/utils
# Copy ONLY the runtime adapter packages (not entire node_modules)
# These are loaded dynamically at runtime via import()
COPY --from=builder /app/node_modules/@debugmcp /app/node_modules/@debugmcp
# Single shared CodeLLDB copy for the rust and cpp adapters (issue #328).
# Both adapters probe their own package roots first (dead in this image) and
# fall back to CODELLDB_PATH; the sibling lldb/ tree next to the binary
# supplies liblldb and the Python support files. "current" is a symlink to
# this image's architecture dir, created in the builder stage.
ENV CODELLDB_PATH=/app/node_modules/@debugmcp/codelldb-common/vendor/codelldb/current/adapter/codelldb
# Fail the image build if the debug engine is missing — the v0.24.0 image
# shipped without CodeLLDB because nothing guarded this (#387).
RUN test -x "$CODELLDB_PATH"
# Rust formatter scripts for CodeLLDB (issue #441) — see the rust-formatters
# stage. The path is a sysroot ROOT: rust.py appends lib/rustlib/etc itself.
COPY --from=rust-formatters /rust-etc /opt/rust-sysroot/lib/rustlib/etc
ENV CODELLDB_RUST_SYSROOT=/opt/rust-sysroot
# Guard (mirrors the CODELLDB_PATH test above): a sysroot without
# lldb_lookup.py would make rust.py silently no-op instead of falling back
# to rustc — fail the build instead.
RUN test -f "$CODELLDB_RUST_SYSROOT/lib/rustlib/etc/lldb_lookup.py"
# Pre-compile JDI bridge for instant Java debugging (no on-demand compilation at runtime)
RUN mkdir -p /app/node_modules/@debugmcp/adapter-java/java/out && \
javac --release 21 \
/app/node_modules/@debugmcp/adapter-java/java/JdiDapServer.java \
-d /app/node_modules/@debugmcp/adapter-java/java/out
# Copy ONLY the production runtime dependencies needed by adapters
# Use a minimal set - the bundle already includes most dependencies
COPY --from=builder /app/node_modules/@vscode /app/node_modules/@vscode
COPY --from=builder /app/node_modules/which /app/node_modules/which
COPY --from=builder /app/node_modules/.pnpm/isexe@4.0.0/node_modules/isexe /app/node_modules/isexe
# Expose ports
EXPOSE 3000 5679
# Copy stdio silencer preloader into runtime image
COPY --from=builder /app/scripts/stdio-silencer.cjs /app/scripts/stdio-silencer.cjs
# Create logs directory with proper permissions for any user, and an empty
# workspace mount point so volume-less runs (e.g. kubectl debug ephemeral
# containers, issue #332) have a valid MCP_WORKSPACE_ROOT directory.
RUN mkdir -p /app/logs /workspace && chmod 777 /app/logs
# Copy entrypoint wrapper (version-controlled script avoids shell quoting pitfalls)
COPY scripts/docker-entry.sh /app/entry.sh
RUN sed -i 's/\r$//' /app/entry.sh && chmod +x /app/entry.sh
# Use tini as PID1 to properly handle signals, then run our wrapper
ENTRYPOINT ["/usr/bin/tini", "--", "/app/entry.sh"]
# Default command arguments
CMD ["stdio"]