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. npm selects the correct per-platform binary at install time — 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.