CodingDevOpsIntermediate30 minSaves 30 minutes

Optimize Node.js 20 pnpm Dockerfiles for Production

For platform engineers shipping Node.js services, generate a multi-stage Dockerfile using Node 20 and pnpm, ensuring cached dependencies and a minimal, secure runtime image for efficient deployments.

Generate a multi-stage Dockerfile for Node.js 20 applications using pnpm, focusing on dependency caching and a compact runtime image. This ensures faster builds and smaller deployment artifacts, critical for production environments. Includes Docker Compose and build argument guidance.

READY-TO-USE PROMPT

Copy Prompt

prompt.txt
Role:
Assume the role of a senior platform engineer specializing in containerizing Node.js applications for production environments. Your expertise encompasses optimizing Docker images for minimal size, enhanced security, and accelerated build performance. You understand the nuances of multi-stage builds and dependency management with tools like pnpm.

Context:
You are tasked with engineering a highly efficient multi-stage Dockerfile for a Node.js 20 application that exclusively utilizes pnpm for its dependency management. The core objectives are paramount: achieving the smallest possible final image size, significantly reducing build times through intelligent dependency caching, and establishing a robustly secure runtime environment. This application is slated for deployment as a critical microservice within a larger system, necessitating adherence to best practices for containerization.

Task:
Your primary task is to generate a comprehensive, production-ready multi-stage Dockerfile. In addition, you must provide a practical `docker-compose.yml` snippet suitable for local development and illustrative build arguments covering common use cases. Conclude with concise, actionable notes detailing the specific image size reduction techniques employed and critical security considerations addressed within the generated Dockerfile.

Constraints:
*   **Base Images:** Strictly use official Node.js 20 images provided by Docker Hub for both the builder and runtime stages. Specify exact versions where possible for reproducibility.
*   **Package Manager:** The application exclusively uses `pnpm` for all dependency installation and management. Ensure `pnpm` is correctly installed and configured within the builder stage.
*   **Build Strategy:** Implement a canonical multi-stage build pattern. Clearly define a 'builder' stage for compilation and dependency installation, and a 'runner' stage for the minimal runtime environment.
*   **Dependency Caching:** Design the Dockerfile to leverage Docker's build cache effectively for `pnpm` dependencies. This involves copying `pnpm-lock.yaml` and `package.json` separately before `pnpm install` to maximize cache hits when only source code changes.
*   **Runtime Image Optimization:** The final image must be meticulously crafted to be as small as possible. It should only contain the absolute necessary runtime dependencies and the compiled application code, excluding all development tools, build artifacts, and unnecessary files from the builder stage.
*   **Security Posture:** Enforce strong security practices. The application must run as a non-root user in the final image. Ensure that sensitive build-time tools and credentials are not present in the production image. Minimize the attack surface by only exposing necessary ports and copying only essential files.
*   **Placeholders:** Integrate the following placeholders for user customization:
    *   `{{application_name}}`: To represent the name of the application directory or the main project folder.
    *   `{{entrypoint_command}}`: For the specific command that starts the Node.js application (e.g., `pnpm start`, `node dist/main.js`).
    *   `{{exposed_port}}`: For the network port the Node.js application listens on (e.g., `3000`, `8080`).

Output:
1.  **Dockerfile:** The complete, fully commented multi-stage Dockerfile. Each stage should be clearly delineated, and key steps (e.g., caching, user creation, cleanup) should have explanatory comments.
2.  **docker-compose.yml Snippet:** A concise `docker-compose.yml` service definition for local development, demonstrating how to build and run the service, referencing the generated Dockerfile. Include port mapping and volume mounts for development if appropriate.
3.  **Build Arguments Examples:** Provide one or two `docker build` commands, illustrating how to pass build arguments if the Dockerfile were to accept them (e.g., for `NODE_ENV` or specific versions, although not strictly required by the current constraints, it's good practice to show).
4.  **Image Size & Security Notes:** A brief, clear explanation detailing the specific Dockerfile instructions that contribute to reducing the final image size and enhancing its security profile. Include recommendations for further hardening or monitoring.

Estimated results

DifficultyIntermediate
Setup time30 min
Time saved30 minutes
Best modelsChatGPT, Gemini, Claude
Best audienceSoftware Development, Cloud Infrastructure

Editor's note

Why this prompt matters

Shipping Node.js applications to production involves more than just writing code. For platform engineers, the challenge lies in creating Docker images that are both lean and secure, especially when dealing with modern Node.js versions and efficient package managers like pnpm. Large image sizes inflate storage costs and deployment times, while inefficient build processes slow down CI/CD pipelines. Security vulnerabilities in base images or unnecessary dependencies add critical risk to production systems. Addressing these issues early in the containerization process is crucial for maintaining operational integrity.

This workflow provides a structured method for engineering optimized multi-stage Dockerfiles specifically for Node.js 20 applications using pnpm. The focus is on achieving a minimal runtime footprint and significantly accelerating build times through intelligent dependency caching. By clearly defining separate builder and runner stages, and carefully managing the pnpm cache, the process ensures that only essential components are included in the final image, reducing its size and improving deployment efficiency.

For platform engineers responsible for the reliability and efficiency of Node.js services, this approach translates directly to faster deployments, reduced resource consumption, and a smaller attack surface. This disciplined methodology minimizes operational overhead and supports scalable, secure microservice architectures, essential for critical production environments.

Anatomy

Prompt engineering breakdown

Role

The prompt establishes the role of a senior platform engineer specializing in containerizing Node.js applications, emphasizing expertise in Docker image optimization for size, security, and build performance, specifically with multi-stage builds and pnpm.

Context

The scenario involves engineering an efficient multi-stage Dockerfile for a Node.js 20 application using pnpm. Key objectives include minimal final image size, reduced build times via dependency caching, and a secure runtime environment for a critical microservice.

Goal

The primary goal is to generate a comprehensive, production-ready multi-stage Dockerfile, a `docker-compose.yml` snippet for local development, illustrative build arguments, and concise notes on image size reduction and security considerations.

Constraints

Strict constraints include using official Node.js 20 Docker Hub images, exclusive use of pnpm, implementing a canonical multi-stage build, effective dependency caching, meticulous runtime image optimization for size, strong security practices (non-root user, minimal attack surface), and integration of specific placeholders for customization.

Output format

The expected output is structured into four distinct sections: a fully commented multi-stage Dockerfile, a `docker-compose.yml` snippet, examples of `docker build` commands with arguments, and a detailed explanation of image size and security measures.

Why this structure works

This structure is effective due to clear role priming, which sets the expert persona for the model. Explicit constraints guide the model toward specific technical requirements like pnpm usage and non-root execution. The detailed output format ensures all necessary components are delivered, making the generated content immediately usable in a production workflow.

Pick your version

Prompt variations

BeginnerWorks with any model

For users new to Docker or multi-stage builds who need a basic, understandable starting point for containerizing a Node.js application.

prompt.txt
As a Docker expert, create a simple multi-stage Dockerfile for a Node.js 20 application that uses pnpm. Focus on making the final image small and secure. The first stage should build the app and install dependencies with pnpm. The second stage should only contain the running application. Include a basic `docker-compose.yml` file for local testing and some notes on how you made the image smaller and more secure.

Make sure to use official Node.js 20 images. The app's main folder is `{{application_name}}`, and it starts with `{{entrypoint_command}}` on port `{{exposed_port}}`. The final image should run as a non-root user.
ProfessionalBest with claude

For experienced platform engineers requiring a production-grade, highly optimized Dockerfile with detailed explanations for a Node.js 20 pnpm application.

prompt.txt
As a senior platform engineer, provide a production-ready, multi-stage Dockerfile for a Node.js 20 application utilizing pnpm. Prioritize minimal image size, accelerated build times through dependency caching, and a secure runtime. The Dockerfile must use official Node.js 20 base images for both builder and runtime, and `pnpm` for all dependency management. Implement a canonical multi-stage pattern, ensuring `pnpm-lock.yaml` and `package.json` are copied early for cache efficiency. The final image must be minimal, run as a non-root user, and exclude development tools.

In addition to the Dockerfile, supply a `docker-compose.yml` snippet for local development and example `docker build` commands. Conclude with specific notes on image size reduction and security hardening techniques employed.

Use `{{application_name}}` for the app directory, `{{entrypoint_command}}` for the start command, and `{{exposed_port}}` for the application port.
Short VersionBest with chatgpt

When a concise, direct prompt is needed to quickly generate a multi-stage Dockerfile without extensive detail or explanation.

prompt.txt
Generate a multi-stage Dockerfile for a Node.js 20 pnpm application. The builder stage should handle `pnpm install` with caching. The final runtime image must be minimal, run as a non-root user, and contain only the application and its runtime dependencies. Provide a basic `docker-compose.yml` for local use. Use `{{application_name}}` for the project, `{{entrypoint_command}}` to start it, and expose `{{exposed_port}}`.
EnterpriseBest with gemini

For organizations with stringent compliance, security auditing, and operational governance requirements for containerized applications.

prompt.txt
As a lead architect for enterprise cloud platforms, develop a auditable and compliant multi-stage Dockerfile for a Node.js 20 application leveraging pnpm. The solution must adhere to corporate security policies, ensuring the smallest possible attack surface, rigorous dependency caching for build efficiency, and a hardened runtime environment. Explicitly state how the design mitigates common container vulnerabilities and supports regulatory compliance requirements. The Dockerfile must use sanctioned Node.js 20 base images and run as a dedicated non-root user in the final stage, excluding all non-runtime binaries.

Include a `docker-compose.yml` snippet for local validation by development and security teams, along with build argument examples. Crucially, provide detailed documentation on image size optimization, security controls, and recommendations for continuous vulnerability scanning and compliance checks. This artifact will be subject to stakeholder review for operational risk assessment.

Integrate `{{application_name}}`, `{{entrypoint_command}}`, and `{{exposed_port}}` as placeholders.

What you'll get

Expected output

```dockerfile # Stage 1: Builder FROM node:20-alpine AS builder WORKDIR /app

# Install pnpm globally RUN npm install -g pnpm

# Copy pnpm lockfile and package.json first for caching COPY package.json pnpm-lock.yaml ./

# Install production dependencies only, leveraging pnpm's content-addressable store RUN pnpm fetch --prod RUN pnpm install --prod --frozen-lockfile

# Copy application source code COPY . .

# If your application has a build step (e.g., TypeScript compilation, Webpack) # RUN pnpm build

# Stage 2: Runner FROM node:20-alpine AS runner WORKDIR /app

# Copy only the necessary production dependencies and built artifacts from the builder stage COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./package.json # If there's a build output, copy it # COPY --from=builder /app/dist ./dist

# Use a non-root user for security best practices USER node

# Expose the port your application listens on EXPOSE 3000

# Command to start the application CMD ["pnpm", "start"] ```

``yaml # docker-compose.yml snippet for local development version: '3.8' services: app: build: context: . dockerfile: Dockerfile args: NODE_ENV: development # Override for development builds ports: - "3000:3000" volumes: - .:/app # Mount host directory for live code changes - /app/node_modules # Prevent host node_modules from overriding container's environment: NODE_ENV: development PORT: 3000 ``

Illustrative Build Argument:

To build for production, you would typically pass NODE_ENV: docker build --build-arg NODE_ENV=production -t my-node-app:1.0.0 .

Notes on Optimization and Security:

  • Image Size Reduction:

* Multi-stage Build: The primary technique, separating the build environment (compiler, dev dependencies) from the minimal runtime. Only essential artifacts (application code, production node_modules) are copied to the final runner image. * Alpine Base Image: node:20-alpine is used for both stages, significantly reducing the base image size compared to Debian-based images. * Pnpm `--prod` flag: pnpm install --prod ensures only production dependencies are installed and copied, omitting development and optional packages.

  • Security Considerations:

* Non-Root User: The USER node directive runs the application as an unprivileged user, mitigating potential container escape vulnerabilities should the application be compromised. * Minimal Base Image: Alpine's smaller footprint inherently reduces the attack surface by including fewer system utilities and libraries. * Frozen Lockfile: pnpm install --frozen-lockfile enforces reproducible dependency installations, preventing unexpected changes or supply chain attacks from modified pnpm-lock.yaml files. * Specific Node.js Version: Pinning to node:20-alpine provides stability and predictable security patching from official Docker images.

Under the hood

Why this prompt works

The prompt's effectiveness stems from its precise structure and technical detail. Role definition immediately aligns the AI's persona with a senior platform engineer, ensuring the output reflects production-minded expertise rather than generic code. This specificity is crucial for generating secure, optimized Dockerfiles.

Context setting clearly outlines the problem: Node.js 20, pnpm, multi-stage builds, and the core objectives of minimal size, fast builds, and security. This targeted scenario prevents the AI from making assumptions or providing generalized solutions.

The tasking is explicit, requesting not just the Dockerfile but also a docker-compose.yml snippet and actionable notes. This comprehensive request ensures all necessary artifacts for development and understanding are provided, reflecting a complete engineering deliverable.

Crucially, detailed constraints guide the AI toward best practices. Specifying "official Node.js 20 images" ensures reliability. Mandating pnpm dictates the correct package manager implementation. The explicit instruction for a "multi-stage build pattern" and, more importantly, the technical guidance on "dependency caching" (copying pnpm-lock.yaml and package.json separately) directly leads to a Dockerfile that optimizes build times and cache utilization. Finally, the "runtime image optimization" constraint pushes for a minimal, secure final image, a key requirement for production deployments. This combination of clear roles, context, tasks, and technical guardrails ensures the generated output is directly usable and adheres to high engineering standards.

Model fit

Best AI models for this prompt

Claude

Claude models excel at generating structured code and detailed explanations. Its ability to follow multi-part instructions and maintain context across a complex task like a multi-stage Dockerfile makes it reliable. It typically produces well-commented and logically organized output, which is crucial for infrastructure code. See the full Claude hub for deeper guidance.

ChatGPT

ChatGPT is proficient in generating practical code snippets and technical documentation. It handles the multi-stage Dockerfile structure effectively and provides clear explanations for each section. While generally good, it may occasionally require minor adjustments to ensure the most up-to-date best practices or specific versioning. See the full ChatGPT hub for deeper guidance.

Gemini

Gemini models are strong at producing coherent code and integrating different components like Dockerfile and Docker Compose. Its capacity to understand and apply constraints, such as specific Node.js versions and pnpm usage, results in accurate and functional outputs. It is also good at explaining the rationale behind design choices. See the full Gemini hub for deeper guidance.

When to use

  • When deploying Node.js 20 microservices to production where image size and build speed are critical.
  • For applications strictly using pnpm for dependency management and requiring efficient caching.
  • When security is a primary concern, necessitating a minimal runtime image and non-root execution.
  • To establish consistent build environments across development, staging, and production.
  • For projects requiring multi-stage builds to separate build-time dependencies from runtime essentials.

When not to use

  • For simple development containers where rapid iteration on code changes is prioritized over image optimization.
  • If your project uses a different package manager like npm or yarn without plans to migrate.
  • For applications that are not Node.js based.
  • In environments where build caching is handled externally and Docker's internal caching is less critical.
  • For very small, non-critical utilities where the overhead of multi-stage builds is unnecessary.

Get more from it

Pro tips

  • 1

    Place `COPY package.json pnpm-lock.yaml .` before `pnpm install` to maximize cache hits when only application code changes, significantly speeding up rebuilds.

  • 2

    Regularly scan base images for known vulnerabilities. Stale base images introduce security risks, making your deployed application susceptible to exploits.

  • 3

    Use `.dockerignore` effectively. Exclude development artifacts, test files, and unnecessary documentation to prevent bloat in the final image.

  • 4

    Pin exact dependency versions in `package.json` and `pnpm-lock.yaml`. This ensures consistent builds across environments, preventing unexpected runtime behavior.

  • 5

    Always run applications as a non-root user in the final stage. This reduces the blast radius in case of a container escape or compromise.

  • 6

    Only expose the necessary port. Limiting exposed ports minimizes the attack surface and adheres to the principle of least privilege.

  • 7

    Implement `pnpm prune --prod` in the builder stage to remove development dependencies before copying to the final image, reducing its footprint.

Don't ship this

Common mistakes

  • Not optimizing `pnpm` caching, leading to full dependency reinstalls on minor code changes.

    Fix — Copy `package.json` and `pnpm-lock.yaml` first, then `pnpm install`, allowing Docker to cache the dependency layer effectively.

  • Running the application as the root user in the final production image, increasing security risk.

    Fix — Create a dedicated non-root user and switch to it using `USER node` (or a custom user) before executing the application.

  • Including build tools and development dependencies in the final runtime image, causing unnecessary bloat.

    Fix — Ensure `pnpm prune --prod` is used and only essential runtime files are copied from the builder stage to the final slim image.

  • Forgetting to use a `.dockerignore` file, resulting in unnecessary files being copied into the build context.

    Fix — Create a comprehensive `.dockerignore` file to exclude `node_modules`, `.git`, temporary files, and other non-essential assets.

  • Using a generic, heavier Node.js base image (e.g., `node:20`) for the final runtime stage.

    Fix — Switch to a minimal base image like `node:20-slim` or `node:20-alpine` for the final stage to drastically reduce image size.

  • Not explicitly setting the `WORKDIR` in both builder and runner stages, causing path inconsistencies.

    Fix — Define `WORKDIR` early in each stage to ensure all subsequent commands execute from the expected directory, improving clarity and reliability.

People also ask

Frequently asked questions

Q.Can this Dockerfile be adapted for other Node.js versions or different base images?

Yes, modify the FROM instructions to specify a different Node.js version (e.g., node:18-slim) or a different base image family. Always verify pnpm compatibility and any specific runtime requirements for your chosen image.

Q.How do I handle private npm packages or registry authentication with pnpm in this setup?

Place your .npmrc file with authentication tokens in the build context. Copy it into the builder stage before pnpm install. Ensure this file is explicitly excluded from the final runtime image for security.

Q.What if my application requires native dependencies that need compilation during the build?

You may need a 'full' Node.js base image (e.g., node:20-bullseye) in the builder stage that includes necessary build tools like gcc or python. Install these tools in the builder stage as needed, then proceed with the slim runtime image.

Q.How can I further reduce the final image size beyond what's already done?

Consider using node:20-alpine as the final base image for maximum size reduction, but be aware of potential glibc compatibility issues. Optimize application code, remove unused assets, and ensure no unnecessary files are copied.

Q.How do I pass environment variables to the running container in production?

Avoid baking secrets or dynamic environment variables directly into the Dockerfile. Instead, use Docker's --env flag, docker-compose.yml environment section, or orchestrator-specific methods (e.g., Kubernetes ConfigMaps/Secrets) at runtime.

Q.Is it possible to use a different package manager like `npm` or `yarn` with this multi-stage pattern?

Yes, the multi-stage pattern is adaptable. You would replace pnpm specific commands (pnpm install, pnpm prune) with their npm or yarn equivalents. Adjust caching strategies accordingly for .npmrc, package-lock.json, or yarn.lock.

Version 1.0Last reviewed July 20, 2026
Reviewed by PromptInFlow Editorial Team