Back to skills
SKILL.md
Nodejs Containers
BSecurityNode.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.
- 58 stars
- 0 votes
- 0 copies
- 2 views
- Added February 8, 2026
Works with
Security analysis
84/100- Accesses sensitive system or user directories
- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add laurigates/claude-plugins --skill nodejs-containers --agent claude-codeAre you the author of Nodejs Containers?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/laurigates-nodejs-containers)---
created: 2026-01-15
modified: 2026-09-24
reviewed: 2026-01-15
name: nodejs-containers
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."
user-invocable: false
allowed-tools: Bash, Read, Grep, Glob, Edit, Write, TodoWrite, WebSearch, WebFetch
---
# 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:
```dockerfile
# 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)
```dockerfile
# 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
```dockerfile
COPY package*.json ./
RUN npm ci --only=production
RUN npm cache clean --force
```
### yarn
```dockerfile
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
RUN yarn cache clean
```
### pnpm
```dockerfile
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.
```dockerfile
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 ci` not `npm 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](REFERENCE.md).
## Related Skills
- `container-development` - General container patterns, multi-stage builds, security
- `go-containers` - Go-specific container optimizations
- `python-containers` - Python-specific container optimizations
Attribution
Comments
Loading comments…