Nub augments the Node already in your image — it is not a separate runtime. The official images start from an official node base and layer Nub on top, so node, npm, and nub are all present.

Official images

FROM ghcr.io/nubjs/nub
COPY --chown=node:node . .
RUN nub install
CMD ["nub", "run", "start"]

The image runs as the non-root node user, so copy your project in with --chown=node:node — a bare COPY . . lands root-owned files that nub install then can't write alongside.

Two variants, both on the current Node release line, digest-pinned and published for linux/amd64 and linux/arm64:

TagBaselibc
latest, <version>, slim, <version>-slimnode:26-slimglibc
alpine, <version>-alpinenode:26-alpinemusl

Run a TypeScript file directly — the entrypoint passes any non-command argument to nub:

docker run --rm -v "$PWD:/app" ghcr.io/nubjs/nub script.ts

Signals reach the runtime: docker stop delivers SIGTERM to nub, which forwards it to the Node process for a clean shutdown. The container runs as the non-root node user (uid 1000); when you bind-mount a directory that nub install must write into, run with --user "$(id -u)" so the writes land with your host ownership.

Adding Nub to your own image

If you already build on a node base, install Nub with one line. At install time, npm selects the correct per-platform binary — including the musl build on Alpine — and the package's postinstall sets the execute bit, so installing as root and then dropping to a non-root user works.

FROM node:26-slim
RUN npm install -g @nubjs/nub

On Alpine, add libgcc and libstdc++ for the native addon:

FROM node:26-alpine
RUN apk add --no-cache libgcc libstdc++ && npm install -g @nubjs/nub

To pin the floor Node version instead of the current line, use a 22 base (node:22-slim or node:22-alpine) — the lowest version Nub's fast tier supports.

Multi-stage builds

Install with nub ci in the build stage. It writes a self-contained node_modules — real files, project-local links — that survives a COPY --from into the final stage. A plain nub install shares packages through a machine-global store the final stage doesn't have, so the copied tree would point at files that aren't there.

FROM node:26-slim AS build
RUN npm install -g @nubjs/nub
WORKDIR /app
COPY . .
RUN nub ci

FROM node:26-slim
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY . .
CMD ["nub", "run", "start"]