Skip to content
Back to skills

Nodejs Containers

BSecurity

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.

  • 58 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
developmentpythongoshellbashsqlreactnextjsnodenodejsdocker

Works with

  • cli

Security analysis

B84/100
  • criticalAccesses sensitive system or user directories
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 25, 2026

npx -y skills add laurigates/claude-plugins --skill nodejs-containers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Nodejs Containers?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Nodejs Containers
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/laurigates-nodejs-containers/badge)](https://www.skillsdirectory.com/skills/laurigates-nodejs-containers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…