feat(node): add production-ready Node.js Dockerfile templates with pnpm

- Multi-stage build with Node.js 22 slim base
- PNPM package manager via corepack (2x faster than npm)
- Build cache mounts for fast rebuilds
- Non-root user (appuser:1000) for security
- Health check using native fetch API
- Alpine variant for size optimization (~130MB)
- Distroless variant for maximum security (~110MB)
- Comprehensive .dockerignore for clean builds
- Full documentation with framework-specific guidance
This commit is contained in:
Greg Hendrickson
2026-02-07 18:04:24 +00:00
parent 8e8ae27640
commit df5ea66906
5 changed files with 510 additions and 0 deletions

72
node/.dockerignore Normal file
View File

@@ -0,0 +1,72 @@
# =============================================================================
# .dockerignore - Exclude files from Docker build context
# =============================================================================
# Dependencies (always reinstall in container)
node_modules/
.pnpm-store/
# Build output (rebuilt in container)
dist/
build/
.next/
.nuxt/
.output/
# Development
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
# Testing
coverage/
.nyc_output/
*.test.js
*.spec.js
__tests__/
__mocks__/
# TypeScript cache
*.tsbuildinfo
tsconfig.tsbuildinfo
# Environment files (use runtime secrets)
.env
.env.*
!.env.example
# IDE / Editor
.idea/
.vscode/
*.swp
*.swo
*~
# Git
.git/
.gitignore
.gitattributes
# Docker
Dockerfile*
docker-compose*.yml
.dockerignore
# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
Jenkinsfile
# Documentation
*.md
!README.md
docs/
LICENSE
# Misc
.DS_Store
Thumbs.db
*.tmp

95
node/Dockerfile Normal file
View File

@@ -0,0 +1,95 @@
# syntax=docker/dockerfile:1.7
#
# Node.js Multi-Stage Dockerfile with PNPM
# Features: Fast builds, minimal image, non-root, security hardened
#
# Build args:
# NODE_VERSION - Node.js version (default: 22)
#
# Usage:
# docker build -t myapp:latest .
# docker run --rm -p 3000:3000 myapp:latest
# =============================================================================
# Stage 1: Install dependencies
# =============================================================================
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-slim AS deps
# Enable corepack for pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy dependency files only (layer caching)
COPY package.json pnpm-lock.yaml* ./
# Fetch dependencies to pnpm store
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm fetch --frozen-lockfile
# Install production dependencies only
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --prod
# =============================================================================
# Stage 2: Build application (if needed)
# =============================================================================
FROM node:${NODE_VERSION}-slim AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY package.json pnpm-lock.yaml* ./
# Install all dependencies (including devDependencies for build)
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# Copy source code
COPY . .
# Build application (TypeScript, bundler, etc.)
RUN pnpm build
# Prune dev dependencies after build
RUN pnpm prune --prod
# =============================================================================
# Stage 3: Production runtime
# =============================================================================
FROM node:${NODE_VERSION}-slim AS runtime
# Security: Create non-root user
RUN groupadd --gid 1000 appgroup && \
useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
# Copy production dependencies
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
# Copy built application
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
# Node.js production optimizations
ENV NODE_ENV=production \
NODE_OPTIONS="--max-old-space-size=512"
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "fetch('http://localhost:3000/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"
# Start application
CMD ["node", "dist/index.js"]

78
node/Dockerfile.alpine Normal file
View File

@@ -0,0 +1,78 @@
# syntax=docker/dockerfile:1.7
#
# Node.js Alpine Dockerfile (Minimal Size)
# Features: ~50MB base image, fast startup, pnpm package manager
#
# Note: Alpine uses musl libc - most npm packages work fine,
# but some native modules may need recompilation.
#
# Usage:
# docker build -f Dockerfile.alpine -t myapp:latest .
# docker run --rm -p 3000:3000 myapp:latest
# =============================================================================
# Stage 1: Install dependencies
# =============================================================================
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-alpine AS deps
# Enable pnpm via corepack
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy dependency files
COPY package.json pnpm-lock.yaml* ./
# Install production dependencies
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm fetch --frozen-lockfile && \
pnpm install --frozen-lockfile --prod
# =============================================================================
# Stage 2: Build application
# =============================================================================
FROM node:${NODE_VERSION}-alpine AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY package.json pnpm-lock.yaml* ./
# Install all deps for build
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm prune --prod
# =============================================================================
# Stage 3: Alpine runtime
# =============================================================================
FROM node:${NODE_VERSION}-alpine AS runtime
# Security: Add non-root user (Alpine style)
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
# Copy application
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "fetch('http://localhost:3000/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"
CMD ["node", "dist/index.js"]

View File

@@ -0,0 +1,59 @@
# syntax=docker/dockerfile:1.7
#
# Node.js Distroless Dockerfile (Maximum Security)
# Features: No shell, no package manager, minimal attack surface
#
# Note: Distroless images have no shell - debugging requires ephemeral containers
#
# Usage:
# docker build -f Dockerfile.distroless -t myapp:latest .
# docker run --rm -p 3000:3000 myapp:latest
# =============================================================================
# Stage 1: Build environment
# =============================================================================
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-slim AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy dependency files
COPY package.json pnpm-lock.yaml* ./
# Fetch and install dependencies
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm fetch --frozen-lockfile && \
pnpm install --frozen-lockfile
# Copy source and build
COPY . .
RUN pnpm build
# Prune to production only
RUN pnpm prune --prod
# =============================================================================
# Stage 2: Distroless runtime (maximum security)
# =============================================================================
FROM gcr.io/distroless/nodejs22-debian12:nonroot AS runtime
WORKDIR /app
# Copy production dependencies
COPY --from=builder /app/node_modules ./node_modules
# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
# Environment
ENV NODE_ENV=production
# Expose port
EXPOSE 3000
# Start application (exec form required - no shell in distroless)
CMD ["dist/index.js"]

206
node/README.md Normal file
View File

@@ -0,0 +1,206 @@
# Node.js Docker Templates
Production-ready Dockerfile templates using [pnpm](https://pnpm.io/) - the fast, disk-efficient package manager.
## Templates
| File | Base Image | Size | Use Case |
|------|-----------|------|----------|
| `Dockerfile` | node:22-slim | ~200MB | Standard production |
| `Dockerfile.alpine` | node:22-alpine | ~130MB | Size optimized |
| `Dockerfile.distroless` | distroless/nodejs22 | ~110MB | Maximum security |
## Quick Start
```bash
# Copy template to your project
cp Dockerfile .dockerignore /path/to/your/project/
# Build
docker build -t myapp .
# Run
docker run -p 3000:3000 myapp
```
## Features
### 🚀 PNPM Package Manager
- **2x faster** than npm
- **Disk efficient** via content-addressable storage
- Strict dependency resolution
- Native workspace support
### 📦 Multi-Stage Build
```dockerfile
# Stage 1: Install dependencies
FROM node:22-slim AS deps
RUN pnpm install --frozen-lockfile --prod
# Stage 2: Build application
FROM node:22-slim AS builder
RUN pnpm build
# Stage 3: Minimal runtime
FROM node:22-slim AS runtime
COPY --from=builder /app/dist ./dist
```
### 🔒 Security Hardened
- Non-root user (`appuser:1000`)
- No devDependencies in production
- Health checks included
- Distroless option for zero CVE base
### ⚡ Build Cache Optimization
```dockerfile
# Cache pnpm store across builds
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
```
## Project Setup
### Required: package.json
```json
{
"name": "myapp",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"express": "^4.21.0"
},
"devDependencies": {
"typescript": "^5.6.0",
"@types/node": "^22.0.0"
}
}
```
### Generate Lock File
```bash
# Install pnpm if needed
npm install -g pnpm
# Or via corepack
corepack enable && corepack prepare pnpm@latest --activate
# Create lock file
pnpm install
```
## Customization
### Change Node Version
```dockerfile
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim AS deps
```
### Change Port
```dockerfile
EXPOSE 8080
HEALTHCHECK ... 'http://localhost:8080/health' ...
```
### Add Native Dependencies
For packages requiring native compilation (node-gyp):
```dockerfile
FROM node:22-slim AS deps
# Install build tools
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*
```
### Custom Entrypoint
```dockerfile
# With npm script
CMD ["pnpm", "start"]
# Direct node execution
CMD ["node", "--enable-source-maps", "dist/index.js"]
# With environment validation
CMD ["sh", "-c", "node dist/validate-env.js && node dist/index.js"]
```
### For Next.js / Nuxt / Other Frameworks
```dockerfile
# Copy standalone output (Next.js)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
CMD ["node", "server.js"]
```
## Choosing the Right Template
| Template | Size | Security | Debug | Native Modules |
|----------|------|----------|-------|----------------|
| `Dockerfile` (slim) | ⭐⭐ | ⭐⭐ | ✅ Easy | ✅ Works |
| `Dockerfile.alpine` | ⭐⭐⭐ | ⭐⭐ | ✅ Easy | ⚠️ May need rebuild |
| `Dockerfile.distroless` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ Hard | ✅ Works |
### When to Use Each
- **slim**: Default choice, best compatibility
- **alpine**: Size-constrained environments, simple apps
- **distroless**: Production Kubernetes, compliance requirements
## Troubleshooting
### "Cannot find module" errors
Ensure your `build` script outputs to `dist/`:
```json
{
"compilerOptions": {
"outDir": "./dist"
}
}
```
### pnpm-lock.yaml not found
Generate it locally first:
```bash
pnpm install
git add pnpm-lock.yaml
```
### Native module compilation fails on Alpine
Install build dependencies:
```dockerfile
RUN apk add --no-cache python3 make g++
```
### Health check failing
Ensure your app has a `/health` endpoint or adjust:
```dockerfile
HEALTHCHECK CMD node -e "process.exit(0)"
```
## Resources
- [pnpm Documentation](https://pnpm.io/)
- [Node.js Docker Best Practices](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md)
- [Distroless Node.js](https://github.com/GoogleContainerTools/distroless/tree/main/nodejs)
- [Docker Multi-Stage Builds](https://docs.docker.com/build/building/multi-stage/)