One repository, two modules (ADR 0069). `node-tools/` holds the runtime — its code, tests, package and the manifest of the module the controller composes a process for on every machine it is assigned to: a bundle of `src/main.js`, the interpreter as a package, a place for the node's credential, the loopback port the console declared, and leave to call every tool. Nothing about how it runs: which bundles to load, where the credential is and whose machine it is are the controller's to compose (WP2). The root module `mesh-tools` keeps the two images TypeScript bundles are compiled in and a module's own service may run in; it is no longer how tools reach a node. As node-tools, `serve` is also the console (ADR 0175 §6): the same process answers MCP on loopback for whoever is on the machine, through which the tools it serves can be called. A module's own runtime in a container keeps serving without a listener. The toolchain image now carries /app/runtime — a package.json saying the compiled files are ES modules and the production node_modules — for the builder to copy into every TypeScript bundle, so a bundle unpacked on a machine starts (ADR 0188 §5; the builder's side is the controller's). Proven here by compiling node-tools with the toolchain's exact flags and starting the result. The AMQP probe script is gone with the bus it probed.
64 lines
3.5 KiB
Docker
64 lines
3.5 KiB
Docker
ARG NODE_BASE=node:22-bookworm-slim
|
|
# The mesh-tools module: the two images every TypeScript module is COMPILED in and may RUN in. The
|
|
# runtime itself ships as the node-tools module's bundle (node-tools/, novox/hq ADR 0175, to-be 38
|
|
# WP3); these images are the toolchain for TypeScript bundles and the base a module's own service
|
|
# may still be built on. They are no longer how tools reach a node.
|
|
#
|
|
# **They were the same image, and that was a mistake.** A module's recipe starts from this and
|
|
# invokes the compiler out of it, so the compiler had to be here — and because the same image was
|
|
# also what every module ran in, every running container on every machine carried a TypeScript
|
|
# compiler it would never invoke. The answer is separate stages rather than one image bad at both
|
|
# jobs. `build.artifacts` in module.json names the published stage each — `toolchain` and `runtime`.
|
|
#
|
|
# The `deps` stage is neither published nor named there: it is where the mesh's package registry is
|
|
# reached, so it is where — and only where — the credential to reach it exists. The toolchain copies
|
|
# resolved node_modules out of it, so the credential is in no image any machine ever holds
|
|
# (novox/hq ADR 0076). This is the buildkit-secret's job done without buildkit, because a machine's
|
|
# docker may carry no buildx.
|
|
|
|
# ---- deps: node_modules resolved from the mesh's registry, credential and all ----------------
|
|
FROM ${NODE_BASE} AS deps
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends git ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
WORKDIR /app
|
|
COPY node-tools/package.json ./
|
|
# The builder writes .npmrc into the build context; it authenticates to the mesh's package registry
|
|
# for the @novox scope, which is where @novox/mesh-sdk resolves. This stage is not published, so the
|
|
# credential travels no further than here. Development dependencies included: the compiler is one.
|
|
COPY .npmrc ./.npmrc
|
|
RUN npm install --no-audit --no-fund
|
|
|
|
# ---- compiling: the runtime's own code built, WITHOUT the credential -------------------------
|
|
FROM ${NODE_BASE} AS compiling
|
|
WORKDIR /app
|
|
COPY node-tools/package.json ./
|
|
# The resolved libraries, but not the .npmrc that resolved them.
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY node-tools/tsconfig.json ./
|
|
COPY node-tools/src ./src
|
|
RUN npm run build
|
|
|
|
# ---- what a running bundle needs, and nothing else -------------------------------------------
|
|
# Its own stage so the toolchain image keeps its build tools while the runtime image does not.
|
|
FROM compiling AS lean
|
|
RUN npm prune --omit=dev
|
|
|
|
# ---- toolchain: what a TypeScript bundle is compiled in ---------------------------------------
|
|
# Beside the compiler, at /app/runtime, what every TypeScript bundle runs with: the production
|
|
# dependencies the SDK and the runtime need, and a package.json saying the compiled files are ES
|
|
# modules. The builder copies this directory whole into a compiled bundle (novox/hq ADR 0188 §5),
|
|
# so a bundle unpacked on a machine starts — a `.js` without that package.json is read as
|
|
# CommonJS, and an import of `nats` without node_modules beside it resolves to nothing.
|
|
FROM compiling AS toolchain
|
|
COPY --from=lean /app/node_modules /app/runtime/node_modules
|
|
RUN printf '{"type":"module","private":true}\n' > /app/runtime/package.json
|
|
|
|
# ---- runtime: what a module's own service may run in ------------------------------------------
|
|
FROM ${NODE_BASE} AS runtime
|
|
WORKDIR /app
|
|
COPY node-tools/package.json ./
|
|
COPY --from=lean /app/node_modules ./node_modules
|
|
COPY --from=compiling /app/dist ./dist
|
|
ENTRYPOINT ["node", "dist/main.js"]
|