From df5ea669066e7cb3e2bc97433ae93dc8dbf3af77 Mon Sep 17 00:00:00 2001 From: Greg Hendrickson Date: Sat, 7 Feb 2026 18:04:24 +0000 Subject: [PATCH] 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 --- node/.dockerignore | 72 +++++++++++++ node/Dockerfile | 95 +++++++++++++++++ node/Dockerfile.alpine | 78 ++++++++++++++ node/Dockerfile.distroless | 59 +++++++++++ node/README.md | 206 +++++++++++++++++++++++++++++++++++++ 5 files changed, 510 insertions(+) create mode 100644 node/.dockerignore create mode 100644 node/Dockerfile create mode 100644 node/Dockerfile.alpine create mode 100644 node/Dockerfile.distroless create mode 100644 node/README.md diff --git a/node/.dockerignore b/node/.dockerignore new file mode 100644 index 0000000..0d5a3cf --- /dev/null +++ b/node/.dockerignore @@ -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 diff --git a/node/Dockerfile b/node/Dockerfile new file mode 100644 index 0000000..f3855d4 --- /dev/null +++ b/node/Dockerfile @@ -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"] diff --git a/node/Dockerfile.alpine b/node/Dockerfile.alpine new file mode 100644 index 0000000..8db7595 --- /dev/null +++ b/node/Dockerfile.alpine @@ -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"] diff --git a/node/Dockerfile.distroless b/node/Dockerfile.distroless new file mode 100644 index 0000000..9a6d333 --- /dev/null +++ b/node/Dockerfile.distroless @@ -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"] diff --git a/node/README.md b/node/README.md new file mode 100644 index 0000000..02abde4 --- /dev/null +++ b/node/README.md @@ -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/)