nodejs-containers
Node.js container optimization — Alpine, multi-stage builds, node_modules caching, BuildKit mounts (900MB to ~100MB). Use when working with Node.js containers or optimizing image sizes.
- License 许可
- MIT
- 最近更新
- 2026-11-13
- Stars
- ★ 60
- Forks
- 6
01 前言 Front Matter
- name
- nodejs-containers
- created
- 1768435200000
- modified
- 1790208000000
- reviewed
- 1768435200000
- description
- Node.js container optimization — Alpine, multi-stage builds, node_modules caching, BuildKit mounts (900MB to ~100MB). Use when working with Node.js containers or optimizing image sizes.
- allowed-tools
- Bash, Read, Grep, Glob, Edit, Write, TodoWrite, WebSearch, WebFetch
- user-invocable
- false
02 资源文件 1
- 参考文档REFERENCE.md6.1 KB
03 SKILL.md 正文
Node.js Container Optimization
Expert knowledge for building optimized Node.js container images using Alpine variants, multi-stage builds, and Node.js-specific dependency management patterns.
When to Use This Skill
| Use this skill when... | Use container-development instead when... |
|---|---|
| Building Node.js-specific Dockerfiles | General multi-stage build patterns |
| Optimizing Node.js image sizes | Language-agnostic container security |
| Handling npm/yarn/pnpm in containers | Docker Compose configuration |
| Dealing with native module builds | Non-Node.js container optimization |
Core Expertise
Node.js Container Challenges:
- Large node_modules directories (100-500MB)
- Full base images include build tools (~900MB)
- Separate dev and production dependencies
- Different package managers (npm, yarn, pnpm)
- Native modules requiring build tools
Key Capabilities:
- Alpine-based images (~100MB vs ~900MB full)
- Multi-stage builds separating build and runtime
- BuildKit cache mounts for node_modules
- Production-only dependency installation
- Non-root user configuration
Optimized Multi-Stage Pattern (Node Servers)
The recommended pattern achieves ~100-150MB images:
# Dependencies stage - production only
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Build stage - includes devDependencies
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage - minimal
FROM node:20-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -u 1001 -S nodejs -G nodejs
# Copy dependencies and built app
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=build --chown=nodejs:nodejs /app/dist ./dist
COPY --chown=nodejs:nodejs package.json ./
USER nodejs
EXPOSE 3000
HEALTHCHECK --interval=30s CMD node healthcheck.js || exit 1
CMD ["node", "dist/server.js"]
BuildKit Cache Mounts (Fastest Builds)
# syntax=docker/dockerfile:1
FROM node:20-alpine AS build
WORKDIR /app
# Cache mount for npm cache
RUN --mount=type=cache,target=/root/.npm \
--mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
Build performance:
- First build: ~2-3 minutes
- Subsequent builds (no package changes): ~10-20 seconds
- Subsequent builds (package changes): ~30-60 seconds
Package Manager Patterns
npm
COPY package*.json ./
RUN npm ci --only=production
RUN npm cache clean --force
yarn
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
RUN yarn cache clean
pnpm
RUN npm install -g pnpm
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
# pnpm creates smaller node_modules with hard links (20-30% smaller)
Next.js on Bun: build with Bun, run with Node
Bun works for the dependency and build stages of a Next.js image, but the
runtime stage that serves .next/standalone must be Node.js. The standalone
output targets Node, and Bun does not resolve the React Server Components SSR
modules it loads (react-dom/server.edge, react-dom/server-rendering-stub,
react-server-dom-webpack/client.edge), so it fails at runtime with
"Could not resolve" errors.
FROM oven/bun:1-debian AS deps # install
FROM oven/bun:1-debian AS builder # next build
FROM gcr.io/distroless/nodejs22-debian12 AS runner
CMD ["server.js"]
Distroless runtime: no shell, so no child_process to CLI tools
A distroless Node image contains Node and nothing else: no /bin/sh, gzip,
pg_dump, or psql. Application code that shells out through
node:child_process (exec, execSync, spawn of a CLI) works in local dev
and fails only in production, with spawn /bin/sh ENOENT. Use Node built-ins
or a library instead:
| Shell tool | In-process replacement |
|---|---|
gzip / gunzip |
node:zlib (gzipSync, createGzip, gunzipSync) |
cat, cp, file writes |
node:fs |
pg_dump / psql |
export/import through the app's database client or ORM |
The same constraint means ops scripts cannot be kubectl exec'd into the
running app pod. Ship them in a separate image and run them as a Job. To stop
the mistake before review, ban the import with a lint rule (for example Biome
noRestrictedImports on node:child_process for server source).
Performance Impact
| Metric | Full Node (900MB) | Alpine (350MB) | Multi-Stage (100MB) | Improvement |
|---|---|---|---|---|
| Image Size | 900MB | 350MB | 100MB | 89% reduction |
| Pull Time | 3m 20s | 1m 10s | 25s | 87% faster |
| Build Time | 4m 30s | 3m 15s | 2m 30s | 44% faster |
| Rebuild (cached) | 2m 10s | 1m 30s | 15s | 88% faster |
| Memory Usage | 512MB | 256MB | 180MB | 65% reduction |
Security Impact
| Image Type | Vulnerabilities | Size | Risk |
|---|---|---|---|
| node:20 (Debian) | 45-60 CVEs | 900MB | High |
| node:20-alpine | 8-12 CVEs | 350MB | Medium |
| Multi-stage Alpine | 4-8 CVEs | 100MB | Low |
| Distroless Node | 2-4 CVEs | 120MB | Very Low |
Agentic Optimizations
| Context | Command | Purpose |
|---|---|---|
| Fast rebuild | DOCKER_BUILDKIT=1 docker build --target build . |
Build only build stage |
| Size check | docker images app --format "table {{.Repository}}\t{{.Size}}" |
Compare sizes |
| Layer analysis | docker history app:latest --human --no-trunc | head -20 |
Find large layers |
| Dependency audit | docker run --rm app npm audit --production |
Check vulnerabilities |
| Cache clear | docker builder prune --filter type=exec.cachemount |
Clear BuildKit cache |
| Test locally | docker run --rm -p 3000:3000 app |
Quick local test |
Best Practices
- Use Alpine variants for smaller images
- Use
npm cinotnpm install(reproducible builds) - Separate dev and production dependencies
- Run as non-root user
- Use multi-stage builds for production
- Layer package.json separately from source code
- Add .dockerignore to exclude node_modules, tests
For detailed examples, advanced patterns, and best practices, see REFERENCE.md.
Related Skills
container-development- General container patterns, multi-stage builds, securitygo-containers- Go-specific container optimizationspython-containers- Python-specific container optimizations
04 同仓库更多 Skills
kitty-show-image
Show the user an image with a note on what to judge, via a file send or a labelled kitty tab. Use when the user must see an image, or icat fails with no /dev/tty.
kitty-session-persistence
Snapshot and restore kitty terminal sessions on macOS. Use when surviving WindowServer hangs without losing tabs, configuring snapshots, or restoring after a force-reboot.
kitty-remote-control
Safe kitty remote control from an agent — read panes with kitty @ ls/get-text, notify, and never mutate the live terminal unasked. Use when running inside kitty.
docs-reference-check
Dangling doc-reference gate: flag markdown naming repo paths or relative links that no longer exist. Use when docs cite moved/deleted files, or wiring a doc-drift lint.
youtube-upload
Upload a video to YouTube via the Data API v3 and edit its title, description, tags or thumbnail afterwards. Use when publishing or retitling a YouTube video.
youtube-studio-share
Share a private YouTube video with a whole Google Workspace organization by scripting YouTube Studio in the browser. Use when a video must be org-only.