DEV Community

IronSoftware
IronSoftware

Posted on

Puppeteer Dockerfile: Solving the 800MB (Issue Fixed)

Developers using Puppeteer for headless browser automation in Docker containers frequently encounter a frustrating reality: their images balloon to 800MB-1.2GB or more. The Chrome/Chromium binary and its dozens of system dependencies consume substantial disk space, creating deployment challenges across CI/CD pipelines, serverless platforms, and container orchestration systems.

This article examines why Puppeteer Docker images grow so large, documents the specific dependencies required, analyzes the performance implications, and presents strategies for reducing image size. For .NET developers, an alternative architectural approach eliminates these containerization headaches entirely.

The Problem

A minimal Node.js Docker image based on node:alpine weighs approximately 40MB. Add Puppeteer with its bundled Chromium, and the image explodes to 800MB-1.2GB. This size increase stems from Chromium's extensive dependency tree, which includes graphics libraries, font rendering systems, and dozens of shared libraries that Chrome requires to function.

The official Puppeteer Docker image (ghcr.io/puppeteer/puppeteer) weighs approximately 950MB due to the Chromium binary alone. Teams attempting to build their own optimized images face a complex maze of system dependencies, many of which are undocumented or discovered only through runtime errors.

The image size problem cascades into multiple operational issues:

  • CI/CD pipeline delays: Pulling a 1GB image adds minutes to build times
  • Cold start latency: Serverless functions using container images suffer initialization delays
  • Storage costs: Container registries charge for storage; larger images increase costs
  • Network bandwidth: Deploying across regions or to edge locations consumes significant bandwidth
  • Local development friction: Developers downloading large images face slow setup times

Dependency Requirements

Running Chromium in Docker requires installing an extensive list of system packages. The official Puppeteer troubleshooting documentation lists these required dependencies for Debian/Ubuntu-based images:

apt-get install -y \
    ca-certificates \
    fonts-liberation \
    libasound2 \
    libatk-bridge2.0-0 \
    libatk1.0-0 \
    libc6 \
    libcairo2 \
    libcups2 \
    libdbus-1-3 \
    libexpat1 \
    libfontconfig1 \
    libgbm1 \
    libgcc1 \
    libglib2.0-0 \
    libgtk-3-0 \
    libnspr4 \
    libnss3 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libstdc++6 \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxcomposite1 \
    libxcursor1 \
    libxdamage1 \
    libxext6 \
    libxfixes3 \
    libxi6 \
    libxrandr2 \
    libxrender1 \
    libxss1 \
    libxtst6 \
    lsb-release \
    wget \
    xdg-utils
Enter fullscreen mode Exit fullscreen mode

For Alpine Linux, the dependency list differs but remains extensive:

apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    font-noto-emoji \
    nodejs \
    yarn
Enter fullscreen mode Exit fullscreen mode

Missing even a single dependency causes cryptic runtime errors like "Could not find Chrome" or "Failed to launch the browser process."

Image Size Breakdown

A typical Puppeteer Docker image breaks down as follows:

Component Size
Base Node.js image (slim) ~150MB
Chromium binary ~400-500MB
System dependencies ~200-300MB
Node modules ~50-100MB
Fonts (CJK support) ~50-100MB
Total 850MB-1.2GB

The Chromium binary alone accounts for roughly half the total image size. Teams targeting multiple architectures (amd64 and arm64) face double the storage requirements.

Cold Start and Resource Impact

Image size directly affects container startup performance, particularly in serverless and auto-scaling environments.

Cold Start Analysis

AWS Lambda functions using container images face initialization overhead proportional to image size:

  • A basic Node.js function (50MB image) cold starts in 0.6-1.4 seconds
  • A Puppeteer function (1GB+ image) cold starts in 3-8 seconds or longer

This delay compounds under load. When traffic spikes trigger new container instances, users experience multi-second delays while the large image initializes. Provisioned Concurrency can mitigate this but adds ongoing costs.

Container Resource Requirements

Chromium's resource consumption extends beyond disk space:

Memory: By default, Docker allocates only 64MB to /dev/shm (shared memory), which is insufficient for Chrome. The browser uses shared memory for inter-process communication, and the default limit causes crashes with errors like "session deleted because of page crash."

# Fix: Increase shared memory
docker run --shm-size=1gb your-puppeteer-image

# Alternative: Disable shared memory usage in Chrome
puppeteer.launch({
  args: ['--disable-dev-shm-usage']
});
Enter fullscreen mode Exit fullscreen mode

CPU: Chrome's multi-process architecture spawns separate renderer processes for each page. Each process has its own CPU overhead, and concurrent page rendering can saturate container CPU limits quickly.

Recommended minimums for production Puppeteer containers:

  • Memory: 1-2GB per container
  • CPU: 1-2 vCPUs
  • Shared memory: 1GB or --disable-dev-shm-usage flag

Resource Consumption at Scale

A service handling 100 concurrent PDF generation requests might require:

  • 100+ Chromium renderer processes
  • 50-100GB of memory across containers
  • Significant CPU allocation for JavaScript execution and rendering

Teams frequently over-provision to handle peak loads, paying for capacity that sits idle during normal traffic.

Deployment Complexity

Getting Puppeteer to run reliably in Docker involves navigating multiple configuration challenges beyond dependency installation.

Sandbox Configuration

Chrome's security sandbox requires specific kernel capabilities that container runtimes often restrict. The official Puppeteer image "requires the SYS_ADMIN capability since the browser runs in sandbox mode."

# Running with sandbox support
docker run --cap-add=SYS_ADMIN your-puppeteer-image

# Alternative: Disable sandbox (security trade-off)
puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});
Enter fullscreen mode Exit fullscreen mode

Disabling the sandbox simplifies deployment but removes Chrome's process isolation security layer.

Platform Architecture Issues

Developers on Apple Silicon (M1/M2/M3) Macs encounter architecture mismatches:

WARNING: The requested image's platform (linux/amd64) does not match
the detected host platform (linux/arm64/v8)
Enter fullscreen mode Exit fullscreen mode

Building for production amd64 targets from arm64 development machines requires:

docker build --platform linux/amd64 -t your-image .
Enter fullscreen mode Exit fullscreen mode

Cross-architecture builds are slower and may behave differently than native builds.

Version Compatibility Challenges

Puppeteer version compatibility with Chromium versions creates a maintenance burden:

"Every major version of Node.js is built over a version of Debian, and that Debian version comes with an old version of Chromium, which could be not compatible with the latest version of Puppeteer."

The Node.js 14 LTS Debian image includes Chromium v90, which may not work with recent Puppeteer versions. Teams must either:

  1. Pin specific Puppeteer versions compatible with their base image's Chromium
  2. Install Chrome directly from Google's repository (adding size)
  3. Use Puppeteer's bundled Chrome (duplicating the binary)

Common Error Messages

Developers encounter various cryptic errors when Puppeteer Docker configuration is incomplete:

Error: Failed to launch the browser process!
/app/node_modules/puppeteer/.local-chromium/linux-*/chrome-linux/chrome:
error while loading shared libraries: libnss3.so: cannot open shared object file

Error: Protocol error (Target.createTarget): Target closed.

Error: Could not find Chrome (ver. 114.0.5735.133).
This can occur if either:
 1. you did not perform an installation before running the script
 2. your cache path is incorrectly configured

Page crashed!
Enter fullscreen mode Exit fullscreen mode

Each error requires investigation to determine which dependency is missing or which configuration is incorrect.

Dockerfile Optimization Strategies

The developer community has documented various approaches to reduce Puppeteer Docker image size.

Strategy 1: Multi-Stage Builds

Separating build and runtime stages can reduce final image size:

# Build stage
FROM node:18-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Runtime stage
FROM node:18-slim
WORKDIR /app

# Install only runtime dependencies
RUN apt-get update && apt-get install -y \
    chromium \
    fonts-liberation \
    libasound2 \
    libatk-bridge2.0-0 \
    libatk1.0-0 \
    libcups2 \
    libdbus-1-3 \
    libgbm1 \
    libgtk-3-0 \
    libnspr4 \
    libnss3 \
    libxcomposite1 \
    libxdamage1 \
    libxrandr2 \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/node_modules ./node_modules
COPY . .

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium

CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

Multi-stage builds typically achieve 30-50% size reduction by excluding build tools from the final image.

Strategy 2: Skip Bundled Chromium

Using the system-installed Chromium instead of Puppeteer's bundled version avoids downloading Chrome twice:

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
Enter fullscreen mode Exit fullscreen mode
const browser = await puppeteer.launch({
  executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});
Enter fullscreen mode Exit fullscreen mode

This approach requires managing Chromium version compatibility manually.

Strategy 3: Alpine-Based Images

Alpine Linux offers smaller base images but introduces compatibility challenges:

FROM node:18-alpine

RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

Limitations: The Chromium version in Alpine 3.20 has documented timeout issues with Puppeteer. Teams have reported needing to downgrade to Alpine 3.19 to avoid these problems.

Strategy 4: Separate Browser Container

Rather than bundling Chromium in the application image, some teams run a separate browser container:

version: '3.8'
services:
  app:
    build: .
    depends_on:
      - chrome
    environment:
      - BROWSER_WS_ENDPOINT=ws://chrome:3000

  chrome:
    image: browserless/chrome:latest
    ports:
      - "3000:3000"
Enter fullscreen mode Exit fullscreen mode
const browser = await puppeteer.connect({
  browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT
});
Enter fullscreen mode Exit fullscreen mode

This approach keeps the application image small but introduces network latency and requires managing an additional service.

Achievable Size Reductions

Approach Typical Image Size Reduction
Naive implementation 1.2GB+ Baseline
Multi-stage build 800MB ~35%
Alpine + optimizations 500-600MB ~50%
Separate browser container 150MB (app only) ~85%

Even with maximum optimization, the application image remains larger than typical Node.js deployments, and the browser component's size persists somewhere in the stack.

Evidence from the Developer Community

The Docker image size problem appears consistently across Puppeteer GitHub issues, Stack Overflow questions, and developer blogs.

Community Reports

"Running Puppeteer in Docker is going to bloat the image size, and there are a lot of tweaks required to make Chromium run correctly (adding user/groups for sandboxing, adjusting memory limits, etc.)"
— Medium article, "Don't let Puppeteer bloat your Docker image"

"Getting headless Chrome up and running in Docker can be tricky. The bundled Chrome for Testing that Puppeteer installs is missing the necessary shared library dependencies."
— DEV Community, "How to use Puppeteer inside a Docker container"

"Docker image size is approximately ~950MB (this is because of the Chromium binary)."
— Puppeteer Sharp Docker documentation

"A Docker image with headless Chrome and Jest can start at 800MB+ in size."
— DEV Community, "How to shrink your Docker images"

GitHub Issues

The Puppeteer repository contains numerous issues related to Docker deployment:

  • Issue #11997: "A better/improved Docker Guide" - requesting clearer documentation
  • Issue #10855: "Unable to use latest Puppeteer in a Docker container"
  • Issue #9149: "Runs perfectly on Docker inside my machine but kept erroring inside Cloud Run"
  • Issue #1793: "docker alpine with node js and chromium headless - puppeteer - failed to launch chrome"
  • Issue #4990: "Puppeteer 1.17 not compatible with node alpine anymore"

The recurring nature of these issues indicates that Docker deployment remains a significant challenge despite years of community documentation.

An Alternative Architecture: Embedded Rendering

For .NET developers, IronPDF offers a fundamentally different approach to containerized PDF generation. Rather than spawning a separate Chromium process with its extensive dependencies, IronPDF embeds the Chrome rendering engine directly within the .NET application.

Why This Architecture Reduces Complexity

IronPDF packages the Chrome rendering components as NuGet dependencies rather than requiring system-level package installation. The IronPdf.Linux package contains pre-compiled binaries optimized for Linux deployment, eliminating the need to manually install Chromium dependencies.

The rendering engine runs in-process, so there are no browser processes to spawn, no WebSocket connections to manage, and no shared memory configuration required. Container configuration becomes straightforward because the application controls all resources within a single process boundary.

Docker Simplification

A complete Dockerfile for IronPDF-based PDF generation:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "YourPdfService.dll"]
Enter fullscreen mode Exit fullscreen mode

Compare this to a Puppeteer Dockerfile, which requires multiple apt-get packages, environment variable configuration, and potentially sandbox capability settings. The IronPDF approach requires no special container configuration.

Code Example

using IronPdf;

// PDF generation service optimized for containerized deployment
public class ContainerizedPdfService
{
    private readonly ChromePdfRenderer _renderer;

    public ContainerizedPdfService()
    {
        // One-time initialization; Chrome engine embedded in process
        _renderer = new ChromePdfRenderer();

        // Configure rendering for production use
        _renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
        _renderer.RenderingOptions.MarginTop = 15;
        _renderer.RenderingOptions.MarginBottom = 15;
    }

    public byte[] GeneratePdfFromHtml(string htmlContent)
    {
        // In-process rendering - no container configuration needed
        // No shared memory, no sandbox flags, no browser process spawning
        var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    }

    public async Task<byte[]> GeneratePdfFromUrlAsync(string url)
    {
        // URL rendering also works without browser process management
        var pdf = await _renderer.RenderUrlAsPdfAsync(url);
        return pdf.BinaryData;
    }

    public byte[] GenerateBatchPdfs(IEnumerable<string> htmlDocuments)
    {
        // Parallel processing without managing browser instances
        var pdfs = htmlDocuments
            .AsParallel()
            .Select(html => _renderer.RenderHtmlAsPdf(html))
            .ToList();

        // Merge into single document
        var merged = PdfDocument.Merge(pdfs);

        // Clean up individual documents
        foreach (var pdf in pdfs)
        {
            pdf.Dispose();
        }

        return merged.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Key points about this code:

  • No Chromium installation or dependency management required
  • No --no-sandbox or --disable-dev-shm-usage flags needed
  • Standard .NET disposal patterns work correctly
  • Parallel operations without browser pool management
  • Works identically on developer machines and production containers

Image Size Comparison

Approach Base Image Final Size
Puppeteer (Node.js) node:slim 800MB-1.2GB
Puppeteer (Alpine) node:alpine 500-600MB
IronPDF (.NET) dotnet/aspnet:8.0 350-450MB

The IronPDF approach produces smaller images while eliminating the operational complexity of managing Chromium dependencies.

API Reference

For more details on the methods used:

Migration Considerations

For Node.js Teams

Teams currently using Puppeteer in Node.js have several options:

  1. Optimize existing Dockerfiles using the strategies documented above
  2. Use a browser-as-a-service like Browserless to offload Chromium management
  3. Evaluate .NET if PDF generation is the primary use case and language flexibility exists

For .NET Teams Using Puppeteer-Sharp

Teams using Puppeteer-Sharp (the .NET port) can migrate to IronPDF with moderate effort:

// Puppeteer-Sharp approach
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = true,
    Args = new[] { "--no-sandbox", "--disable-dev-shm-usage" }
});
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
var pdfBytes = await page.PdfDataAsync();

// IronPDF approach
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
var pdfBytes = pdf.BinaryData;
Enter fullscreen mode Exit fullscreen mode

The IronPDF API is more concise because browser lifecycle management is handled internally.

Licensing

IronPDF is commercial software with per-developer licensing. Organizations should evaluate licensing costs against:

  • DevOps time spent managing Puppeteer Docker configurations
  • Infrastructure costs from larger images and over-provisioned containers
  • Developer productivity lost to Docker debugging

A free trial allows testing with production workloads before commitment.

What You Gain

  • Container images 50-70% smaller than Puppeteer equivalents
  • No system-level dependency installation
  • No sandbox or shared memory configuration
  • Consistent behavior across development and production
  • Standard .NET deployment patterns

What to Consider

  • IronPDF is specific to PDF operations; general browser automation requires different tools
  • Initial render incurs Chrome engine initialization (subsequent renders are faster)
  • Some Puppeteer features for page interaction have no direct equivalent
  • Requires .NET runtime (not applicable for Node.js-only environments)

Conclusion

Puppeteer's Docker image size problem stems from Chromium's extensive dependency tree and the architectural decision to spawn a separate browser process. While optimization strategies can reduce images from 1.2GB to 500-600MB, the fundamental complexity of managing Chromium in containers remains.

For .NET developers whose primary need is PDF generation, IronPDF offers an alternative architecture that embeds the rendering engine directly, producing smaller images with simpler Dockerfiles. The in-process approach eliminates the container configuration challenges that make Puppeteer deployment frustrating.

Teams should evaluate whether the browser automation capabilities Puppeteer provides justify its containerization overhead, or whether a purpose-built PDF library better fits their requirements.


Written by Jacob Mellor, CTO at Iron Software, who leads the technical development of IronPDF and has over 25 years of experience building developer tools.


References

  1. Puppeteer Docker Guide{:rel="nofollow"} - Official Docker documentation
  2. Puppeteer Troubleshooting{:rel="nofollow"} - Dependency and configuration issues
  3. Don't let Puppeteer bloat your Docker image{:rel="nofollow"} - Image size optimization guide
  4. How to use Puppeteer inside a Docker container{:rel="nofollow"} - DEV Community tutorial
  5. Puppeteer performance in AWS Lambda Docker containers{:rel="nofollow"} - Cold start analysis
  6. Running Puppeteer in Docker: A Simple Guide{:rel="nofollow"} - Image size documentation
  7. puppeteer/docker/Dockerfile{:rel="nofollow"} - Official Dockerfile reference
  8. GitHub Issue #11997: A better/improved Docker Guide{:rel="nofollow"} - Community documentation requests
  9. GitHub Issue #1793: Alpine Linux compatibility{:rel="nofollow"} - Alpine-specific issues
  10. AWS Lambda Container Image Cold Starts{:rel="nofollow"} - Serverless deployment patterns
  11. IronPDF Docker Deployment - Container configuration for .NET
  12. ChromePdfRenderer API Reference - IronPDF rendering documentation

For the latest IronPDF documentation and tutorials, visit ironpdf.com.

Top comments (0)