WarpBuild LogoWarpBuild Docs

Node.js with pnpm

Best practices for Dockerfile for Node.js with pnpm

🐳 Annotated Dockerfile for Node.js with pnpm:

# Use Node.js LTS as the base image for consistency and long-term support
FROM node:lts-jod AS base
 
# Stage 1: Install production dependencies using pnpm
FROM base AS deps
 
# Enable corepack (manages package managers like pnpm)
RUN corepack enable
 
# Set working directory
WORKDIR /app
 
# Copy only package definition files first
COPY package.json pnpm-lock.yaml ./
 
# Cache the pnpm store for faster dependency fetches across builds
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
 pnpm fetch --frozen-lockfile
 
# Install only production dependencies
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
 pnpm install --frozen-lockfile --prod
 
# Stage 2: Build the application
FROM base AS build
 
# Enable corepack (manages package managers like pnpm)
RUN corepack enable
 
WORKDIR /app
 
# Copy package definitions to maintain consistent build context
COPY package.json pnpm-lock.yaml ./
 
# Reuse cached pnpm store for quick dependency installation
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
 pnpm fetch --frozen-lockfile
 
# Install all dependencies (dev + prod) for building the app
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
 pnpm install --frozen-lockfile
 
# Copy entire source code
COPY . .
 
# Run build script defined in your package.json (generally builds into a "dist" directory)
RUN pnpm build
 
# Stage 3: Create the final lightweight production image
FROM base
 
# Set working directory
WORKDIR /app
 
# Copy only production dependencies (no dev dependencies)
COPY --from=deps /app/node_modules /app/node_modules
 
# Copy compiled application output (dist directory)
COPY --from=build /app/dist /app/dist
 
# Explicitly set environment to production
ENV NODE_ENV production
 
# Default command to run your Node.js application
CMD ["node", "./dist/index.js"]

🔍 Why these are best practices:

✅ Multi-stage builds

  • Smaller final images: Dependencies and build tools are discarded after use, reducing container size.
  • Security: Fewer files and tools mean a smaller attack surface.

✅ Caching pnpm store

  • Faster builds: Reusing a cached store reduces install times drastically, especially beneficial for large dependency trees.
  • Lower CI/CD overhead: Speeds up continuous integration and deployment workflows.

✅ Separating dependencies and build stages

  • Clear separation of concerns: Each stage serves a single purpose, making it easier to debug and optimize.
  • Improved cache efficiency: Changes in code don’t trigger unnecessary reinstallation of unchanged dependencies.

✅ Minimal runtime image

  • Performance and security: Only the essential runtime code is present, limiting potential vulnerabilities.
  • Lower resource consumption: Optimized resource usage in production deployments.

🚀 Additional Dockerfile best practices you can adopt:

Use a non-root user

For enhanced security, run your app as a non-root user:

FROM base
 
# Create a non-root user
RUN useradd -m appuser
 
WORKDIR /app
 
COPY --from=deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
 
ENV NODE_ENV production
 
# Switch to non-root user
USER appuser
 
CMD ["node", "./dist/index.js"]

Use HEALTHCHECK directive

Allows Docker to monitor container health automatically.

HEALTHCHECK --interval=30s --timeout=3s \
 CMD curl -f http://localhost:3000/health || exit 1

Use explicit .dockerignore

Prevent copying unnecessary files into your image.

Example .dockerignore

node_modules dist coverage .git Dockerfile docker-compose.yml README.md *.log


#### Set resource limits explicitly

When deploying containers, always set CPU and memory limits to avoid resource starvation or instability.

Example in Kubernetes or Docker Compose (outside Dockerfile)

```yaml
resources:
  limits:
    cpu: 1000m
    memory: 1Gi

Consider using Distroless or Alpine images

Switch to even lighter-weight base images if you’re comfortable handling potential compatibility issues:

FROM node:22-alpine AS base

Or distroless:

FROM gcr.io/distroless/nodejs22-debian12 AS final

By following these annotations and best practices, your Docker images become faster to build, more secure, smaller, and easier to maintain—ideal for modern production workflows.

Last updated on

On this page