Docker
Official Nub Docker images, and how to add Nub to your own image. The images install Nub onto an official Node base, so Node is present and the runtime is augmented out of the box.
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:0.7.5
COPY --chown=node:node package.json package-lock.json ./
RUN nub ci
COPY --chown=node:node . .
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:
| Tag | Base | libc |
|---|---|---|
latest, <version>, slim, <version>-slim | node:26-slim | glibc |
alpine, <version>-alpine | node:26-alpine | musl |
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.tsSignals reach the runtime: docker stop delivers SIGTERM to nub, which forwards it to the Node process for a clean shutdown. The node user is uid 1000, so when you bind-mount a directory that nub install must write into, run with --user "$(id -u)" and 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/nubOn Alpine, add libgcc and libstdc++ for the native addon:
FROM node:26-alpine
RUN apk add --no-cache libgcc libstdc++ && npm install -g @nubjs/nubTo 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.
Web services
Container platforms route traffic to the port they provide through PORT. Bind the server to 0.0.0.0, not 127.0.0.1:
import { createServer } from "node:http";
const host = "0.0.0.0";
const port = Number(process.env.PORT || 8080);
createServer((request, response) => {
if (request.url !== "/alive") {
response.writeHead(404);
response.end();
return;
}
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({
alive: true,
runtime: "nub-on-node",
}));
}).listen(port, host);Copy manifests before source files so dependency installation stays cached. The image runs as node, so use --chown=node:node for files the install must write beside:
FROM ghcr.io/nubjs/nub:0.7.5
COPY --chown=node:node package.json package-lock.json ./
RUN nub ci
COPY --chown=node:node . .
EXPOSE 8080
CMD ["nub", "run", "start"]Change package-lock.json to the project's existing lockfile. Exclude node_modules, .git, and local .env files with .dockerignore.
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 ghcr.io/nubjs/nub:0.7.5 AS build
COPY --chown=node:node package.json package-lock.json ./
RUN nub ci
COPY --chown=node:node . .
RUN nub run build
FROM ghcr.io/nubjs/nub:0.7.5
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/package.json ./package.json
CMD ["nub", "run", "start"]Read the full docs on docs.docker.com.
GitHub Action
A drop-in replacement for the official setup-node action. Swap one line in your workflow and Nub installs itself, provisions the project's pinned Node, and fronts it on PATH so every step keeps working unchanged.
Vercel
Vercel ships no preinstalled Nub, but the install command is yours to replace — so one line puts the CLI on PATH for the rest of the build. The devDependency pattern works too.