GitNexus/Dockerfile.cli
Shifra Williams f2717c6a7c
Some checks are pending
Scorecard / Scorecard analysis (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Skill copy sync / shipped skills drift guard (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
feat(render): add one-click deploy to render support (#2804)
2026-08-06 00:19:44 +00:00

131 lines
7.2 KiB
Text

ARG BUILDPLATFORM
ARG TARGETPLATFORM
# Pinned npm version used to replace the bundled npm in the upstream Node
# image. Bumping requires a coordinated update in Dockerfile.web and
# gitnexus/Dockerfile.test so all images bootstrap the same npm.
ARG NPM_VERSION=11.14.1
# -- Builder -----------------------------------------------------------
# Native modules (tree-sitter-*, onnxruntime-node, node-gyp builds for
# tree-sitter-proto / tree-sitter-swift) require python3 + a C/C++ toolchain.
# node:22-bookworm-slim
FROM node:22-bookworm-slim@sha256:9f6d5975c7dca860947d3915877f85607946403fc55349f39b4bc3688448bb6e AS builder
ARG NPM_VERSION
WORKDIR /app
RUN npx --yes npm@${NPM_VERSION} install -g npm@${NPM_VERSION}
# Toolchain for node-gyp / native builds.
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ git && rm -rf /var/lib/apt/lists/*
# Build gitnexus-shared first - gitnexus depends on it as a workspace.
COPY gitnexus-shared/package.json gitnexus-shared/package-lock.json ./gitnexus-shared/
RUN npm ci --prefix gitnexus-shared
COPY gitnexus-shared ./gitnexus-shared
RUN rm -f gitnexus-shared/tsconfig.tsbuildinfo
RUN npm run build --prefix gitnexus-shared
# Copy the full gitnexus package before installing - `npm ci` triggers
# `postinstall` (patches tree-sitter-swift, builds the vendored
# tree-sitter-proto) and `prepare` (compiles TypeScript via scripts/build.js),
# both of which need the source tree.
COPY gitnexus ./gitnexus
RUN npm ci --prefix gitnexus
# Drop dev dependencies for a smaller runtime layer.
RUN npm prune --omit=dev --prefix gitnexus
# `npm prune` removes anything not in package.json's dependency tree — which
# includes the VENDORED tree-sitter grammars (materialized into node_modules/ by
# postinstall, but not declared as deps) and their freshly-built native bindings.
# The `serve` image analyzes/parses uploaded repos at runtime, so those grammars
# must survive into the runtime layer. Re-run the grammar postinstall here in the
# builder (which still has python3/make/g++ and the hoisted node-addon-api /
# node-gyp-build) to re-materialize + rebuild them after the prune. This is
# load-bearing for tree-sitter-c (a core, REQUIRED grammar now vendored, #2116):
# as a former `dependency` it used to survive prune; vendored, it would not.
RUN npm run postinstall --prefix gitnexus
# -- Runtime -----------------------------------------------------------
# node:22-bookworm-slim
FROM node:22-bookworm-slim@sha256:9f6d5975c7dca860947d3915877f85607946403fc55349f39b4bc3688448bb6e AS runtime
# curl for the healthcheck; git for cloning; ca-certificates for TLS verification.
RUN apt-get update && apt-get install -y --no-install-recommends curl git ca-certificates && rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/local/lib/node_modules/npm \
&& rm -rf /usr/local/lib/node_modules/corepack \
&& rm -f /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack
WORKDIR /app
# Pre-create the data directory and hand it to the unprivileged `node` user
# so the bind-mounted volume is writable without root.
RUN mkdir -p /data/gitnexus && chown -R node:node /data
COPY --from=builder --chown=node:node /app/gitnexus/dist ./gitnexus/dist
COPY --from=builder --chown=node:node /app/gitnexus/node_modules ./gitnexus/node_modules
COPY --from=builder --chown=node:node /app/gitnexus/package.json ./gitnexus/package.json
COPY --from=builder --chown=node:node /app/gitnexus/scripts/install-duckdb-extension.mjs ./gitnexus/scripts/install-duckdb-extension.mjs
COPY --from=builder --chown=node:node /app/gitnexus/vendor ./gitnexus/vendor
# Expose the `gitnexus` binary on PATH so the documented Docker workflow
# (`docker compose exec gitnexus-server gitnexus index /workspace/<repo>`)
# works without users having to invoke `node /app/gitnexus/dist/cli/index.js`.
# `npm prune --omit=dev` in the builder stage strips `node_modules/.bin/`
# entries, so the `gitnexus` bin declared in package.json (`dist/cli/index.js`,
# which already carries `#!/usr/bin/env node` and 755 perms) is otherwise
# unreachable from $PATH.
RUN ln -s /app/gitnexus/dist/cli/index.js /usr/local/bin/gitnexus
# Bake the LadybugDB FTS extension into the image so BM25 keyword search works
# at runtime. The server runs the default `load-only` extension policy (the read
# pool pins `{ policy: 'load-only' }`), so a runtime `LOAD EXTENSION fts` never
# INSTALLs — the extension must already exist in the runtime user's HOME
# extension dir, or every keyword search silently degrades (no FTS indexes are
# written and ranking falls back to vector-only with only a `warning` field).
# Run the installer as the `node` user with the SAME HOME the server runs under,
# so `INSTALL fts` materializes the extension under `$HOME/.lbdb/extension` where
# the runtime `LOAD` resolves it offline. `ENV HOME` is pinned because Docker
# does not derive HOME from `USER`, so without it build-install and runtime-load
# would resolve different paths. Requires network egress for the one-time
# INSTALL; the build fails loudly if it cannot fetch the extension. The DB-size
# default comes from GITNEXUS_LBUG_MAX_DB_SIZE (single source of truth, matches
# the runtime) — it only sizes the throwaway scratch DB used to run INSTALL.
# The second `--verify-only` step re-LOADs the extension in a FRESH process
# under the same HOME, so a HOME/extension-dir mismatch fails the build here
# rather than silently degrading keyword search to vector-only at runtime.
ENV HOME=/home/node \
GITNEXUS_LBUG_MAX_DB_SIZE=17179869184
RUN su node -s /bin/sh -c "HOME=/home/node node /app/gitnexus/scripts/install-duckdb-extension.mjs fts" \
&& su node -s /bin/sh -c "HOME=/home/node node /app/gitnexus/scripts/install-duckdb-extension.mjs fts --verify-only"
# Published runtime assets (in package.json `files`). Placed AFTER the DuckDB
# FTS-extension RUN above so editing hook/skill content does not invalidate that
# network-fetching cache layer; they have no input dependency on it.
# `hooks/`: dist/cli/resolve-invocation.js does
# `require('../../hooks/claude/resolve-analyze-cmd.cjs')` at module load — the
# single source of truth for the npm-11 npx-crash invocation decision (#1939).
# Without it, `gitnexus analyze` inside the image crashes with MODULE_NOT_FOUND
# before it does any work (#2130). `skills/`: the CLI reads the bundled SKILL.md
# templates from `<pkg>/skills/` for `gitnexus analyze --skills` and `gitnexus
# setup`/`uninstall`; absent, those degrade silently (placeholder content / zero
# skills installed). (The web UI bundle `web/`, also in `files`, is deliberately
# NOT shipped: this builder never builds gitnexus-web, so the image is API-only;
# the UI is the separate Dockerfile.web image / hosted app.)
COPY --from=builder --chown=node:node /app/gitnexus/hooks ./gitnexus/hooks
COPY --from=builder --chown=node:node /app/gitnexus/skills ./gitnexus/skills
USER node
# The web UI defaults to http://localhost:4747 - keep that contract.
ENV GITNEXUS_HOME=/data/gitnexus \
NODE_ENV=production \
PORT=4747
EXPOSE 4747
# Bind 0.0.0.0 for the host's mapped port, honoring an injected $PORT (Render
# sets one). `sh -c` expands it; `exec` keeps the server PID 1 so SIGTERM still
# reaches it. Platforms can rely on this instead of a dockerCommand override.
CMD ["sh", "-c", "exec gitnexus serve --host 0.0.0.0 --port \"${PORT:-4747}\""]