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