skills/node-deploy/SKILL.md
Build and deploy Node.js applications — version detection, package managers, framework-specific builds, monorepo support, and Dockerfile patterns. Use when deploying a Node.js, JavaScript, or TypeScript project, or when package.json is detected in the repository.
npx skillsauth add nixopus/agent node-deployInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Project is Node.js if package.json exists in the root directory.
Node.js version priority:
.node-version or .nvmrc fileengines.node field in package.jsonmise.toml or .tool-versionsIf Bun is detected as the package manager:
engines.bun field in package.json.bun-version filemise.toml or .tool-versionsWhen Bun is the primary runtime, Node.js must still be installed if:
packageManager field exists in package.json)node-gyp)package.json script explicitly invokes nodeDetect in order:
packageManager field in package.json → use Corepack to install exact version (e.g. [email protected])| Lock file | Package manager |
|---|---|
| package-lock.json | npm |
| yarn.lock | Yarn (check .yarnrc.yml to distinguish Yarn Berry from Classic) |
| pnpm-lock.yaml | pnpm |
| bun.lockb or bun.lock | Bun |
engines field — engines.pnpm → pnpm, engines.bun → Bun, engines.yarn → Yarn| Package manager | With lockfile | Without lockfile |
|---|---|---|
| npm | npm ci | npm install |
| yarn (classic) | yarn install --frozen-lockfile | yarn install |
| yarn (berry) | yarn install --immutable | yarn install |
| pnpm | pnpm install --frozen-lockfile | pnpm install |
| bun | bun install --frozen-lockfile | bun install |
Set NODE_ENV=production for the runtime stage. During the build, keep NPM_CONFIG_PRODUCTION=false and YARN_PRODUCTION=false so dev dependencies remain available for compilation. Disable update notifications with NPM_CONFIG_UPDATE_NOTIFIER=false and NPM_CONFIG_FUND=false. Set CI=true to enable CI-appropriate behavior in tooling.
start script in package.jsonmain or module field in package.json (run with node)server.js, index.js, or index.ts in root (run with node)build script in package.json → ${packageManager} run build| Framework | Output directory |
|---|---|
| NestJS | dist |
| Next.js (SSR) | .next |
| Next.js (export) | out |
| Nuxt | .output |
| SvelteKit | build |
| Remix | build |
| Astro | dist |
| Vite | dist |
| Angular | dist/${projectName} |
| React (CRA) | build |
| React Router | build/client |
| Default | dist |
PORT=<number> from .env, .env.example, .env.productionpackage.json scripts — scan start, dev, serve for -p <port>, --port <port>, PORT=<port>next.config.* or vite.config.* for port: <number>| Framework | Default port | |---|---| | Express | 3000 | | Fastify | 3000 | | NestJS | 3000 | | Hono | 3000 | | Next.js | 3000 | | Nuxt | 3000 | | Remix | 3000 | | SvelteKit | 5173 | | Astro | 4321 | | Vite | 5173 | | React | 3000 | | Vue | 8080 |
From package.json dependencies (merge dependencies + devDependencies). First match wins.
| Package pattern | Framework | Category |
|---|---|---|
| express | Express | Backend |
| fastify | Fastify | Backend |
| @nestjs/core | NestJS | Backend |
| hono | Hono | Backend |
| next | Next.js | FullStack |
| nuxt | Nuxt | FullStack |
| @sveltejs/kit | SvelteKit | FullStack |
| @remix-run/node or @remix-run/react | Remix | FullStack |
| astro | Astro | Static |
| vite (without a higher framework) | Vite | Frontend |
| react + react-dom (without Next/Remix) | React | Frontend |
| vue (without Nuxt) | Vue | Frontend |
Config file fallback:
| Config file | Framework |
|---|---|
| nest-cli.json | NestJS |
| next.config.js, next.config.mjs, next.config.ts | Next.js |
| nuxt.config.js, nuxt.config.ts | Nuxt |
| svelte.config.js | SvelteKit |
| remix.config.js | Remix |
| astro.config.mjs, astro.config.js | Astro |
| vite.config.ts, vite.config.js | Vite |
| vue.config.js | Vue |
| angular.json | Angular |
Next.js
next.config.* for output: "standalone" → standalone build (smaller image, includes node_modules subset)output: "export" → static site, no server needed.next/cache between buildsapp/ directory → React Server ComponentsNuxt
node .output/server/index.mjsnode_modules/.cacheAstro
output is not "server" → static sitenode_modules/.astroworkspaces field in root package.jsonpnpm-workspace.yamlturbo.json, nx.json, lerna.json, rush.jsonapps/, packages/, services/pnpm-workspace.yaml → packages: listworkspaces field in root package.jsonpackage.json files + root lock file)node_modulesAlways copy:
package.json (root + workspace packages if monorepo)pnpm-workspace.yaml (if pnpm monorepo).npmrc (if exists — contains registry config)Framework-specific install files (copy if they exist, they trigger postinstall):
prisma/schema.prisma — Prisma generates client on postinstall.env files needed at build time (e.g. Next.js NEXT_PUBLIC_*)If package.json defines preinstall or postinstall scripts that depend on source files, copy the entire source before install to avoid broken hooks.
| Framework | Detection | Default output dir |
|---|---|---|
| CRA | react-scripts in deps | build |
| Vite | vite.config.js/ts or build script contains vite build | dist |
| Angular | angular.json | dist/${projectName} |
| Astro | astro.config.* and output is not "server" | dist |
| Next.js (export) | output: "export" in config | out |
| React Router | react-router.config.* (ssr: false for SPA) | build/client |
Serve with Caddy/nginx. SPA fallback, cache headers for hashed assets, gzip/brotli.
| Framework | Build-time prefix | Runtime access |
|---|---|---|
| Next.js | NEXT_PUBLIC_* | process.env.* (server only) |
| Nuxt | NUXT_PUBLIC_* | process.env.* via useRuntimeConfig() |
| Vite | VITE_* | not available at runtime (build-only) |
| SvelteKit | PUBLIC_* | $env/static/public (build-only) |
| Astro | PUBLIC_* | import.meta.env.* (build-only) |
| CRA | REACT_APP_* | not available at runtime (build-only) |
Build-time env vars must be available during Docker build step (via ARG + ENV).
| Package | Required system packages |
|---|---|
| Puppeteer | Chromium, xvfb, font libraries, Chrome system deps |
| Playwright | Chromium headless shell, system packages |
| sharp | libvips and build tools |
| bcrypt | python3, make, g++ |
| canvas | libcairo2-dev, libjpeg-dev, libpango1.0-dev, libgif-dev, build-essential |
After build, remove dev dependencies to reduce image size:
npm prune --omit=devyarn install --production or set NODE_ENV=production during installpnpm prune --prodbun install --productionSkip pruning if the start command references a dev dependency (ts-node, tsx, nodemon).
| Framework | Cache directory |
|---|---|
| NestJS | node_modules/.cache |
| Next.js | .next/cache |
| Nuxt | node_modules/.cache |
| SvelteKit | node_modules/.cache |
| Remix | .cache |
| React Router | .react-router |
| Astro | node_modules/.astro |
| Vite | node_modules/.vite |
| Default | node_modules/.cache |
FROM node:<version>-slim AS base
FROM base AS deps
WORKDIR /app
COPY package.json <lockfile> ./
RUN <install-command>
FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN <build-command>
FROM base AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
EXPOSE <port>
CMD ["node", "dist/index.js"]
FROM node:<version>-slim AS base
FROM base AS deps
WORKDIR /app
COPY package.json <lockfile> ./
RUN <install-command>
FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN <build-command>
FROM base AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
FROM node:<version>-slim AS build
WORKDIR /app
COPY package.json <lockfile> ./
RUN <install-command>
COPY . .
RUN <build-command>
FROM caddy:alpine AS runtime
COPY --from=build /app/<output-dir> /srv
COPY Caddyfile /etc/caddy/Caddyfile
EXPOSE 80
node_modules won't exist unless nodeLinker: node-modules is set in .yarnrc.ymlnpm ci deletes node_modules before installing — copy package.json + lockfile first, install, then copy source for proper layer cachingoutput: "standalone" must be set in config BEFORE running the build — the build step generates the standalone directoryprisma generate on postinstall — prisma/schema.prisma must be copied before npm install--shamefully-hoist may be needed for packages expecting a flat node_modules layout--frozen-lockfile is the correct flag (not --ci like npm)tools
Compressed catalog of all Nixopus API operations for the nixopus_api() tool
development
Deploy static file sites — Caddy/nginx serving, Staticfile config, and Dockerfile patterns. Use when deploying a static HTML site with no server-side runtime, or when index.html or a Staticfile is detected at the project root.
devops
Deploy shell script applications — interpreter detection, setup scripts, and Dockerfile patterns. Use when deploying a shell script project, or when start.sh is detected.
development
Self-healing loop for failed deployments — diagnose, fix, redeploy up to 3 attempts, then escalate or rollback. Load when a deployment fails or build errors occur.