DEV Community

IronSoftware
IronSoftware

Posted on

Puppeteer Docker Alpine Chromium Not Working (Issue Fixed)

Developers attempting to run Puppeteer or Puppeteer-Sharp in Alpine Linux Docker containers frequently encounter cryptic errors: "Failed to launch chrome!", symbol resolution failures, missing shared libraries, and protocol timeouts. These issues stem from a fundamental incompatibility between Alpine's musl C library and Chromium's glibc-based binaries. What appears to be a straightforward container optimization exercise often becomes a multi-day debugging session.

The Problem

Alpine Linux is popular for Docker deployments because of its minimal footprint. A base Alpine image weighs approximately 5MB compared to Debian's 125MB. For microservices architectures where dozens of containers run simultaneously, the disk and memory savings compound significantly. However, this efficiency comes from using musl libc instead of the more common glibc implementation.

Chromium and its derivatives are compiled against glibc. When Puppeteer downloads its bundled Chrome for Testing binary, that binary expects glibc system calls and shared library interfaces that do not exist in Alpine's musl environment. The Chromium binary loads, attempts to resolve symbols, and crashes with errors that provide minimal diagnostic information.

Alpine does package its own Chromium build compiled against musl, but this creates a version matching problem. Puppeteer expects specific Chrome versions with matching DevTools Protocol interfaces. The Alpine-packaged Chromium may be several versions behind, causing protocol mismatches, missing API methods, and timeout errors during browser communication.

Error Messages and Symptoms

Failed to launch chrome!
Error relocating /usr/lib/chromium/chrome: hb_font_funcs_set_glyph_h_advances_func: symbol not found
Error relocating /usr/lib/chromium/chrome: hb_font_set_variations: symbol not found

error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file
error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file

ProtocolError: Network.enable timed out. Increase the 'protocolTimeout' setting in launch/connect
Target closed.

PuppeteerSharp.ProcessException: Failed to launch browser!
chrome_crashpad_handler: --database is required

spawn /usr/bin/chromium-browser EAGAIN
qemu: uncaught target signal 5 (Trace/breakpoint trap) - core dumped
Enter fullscreen mode Exit fullscreen mode

The harfbuzz (hb_) symbol errors indicate library version mismatches within the Alpine package repositories. The shared library errors appear when using Puppeteer's bundled Chromium rather than Alpine's packaged version. Protocol timeouts occur when Chromium starts but cannot communicate properly with the Node.js or .NET process.

Who Is Affected

The Alpine compatibility issue impacts developers in specific scenarios where container size and resource efficiency drive technical decisions.

Microservices deployments running multiple containerized services benefit most from Alpine's small footprint. A Kubernetes cluster with 50 pods saves gigabytes of storage and network transfer using Alpine instead of Debian. When one of these services requires PDF generation via Puppeteer, the Alpine compatibility problem surfaces.

CI/CD pipelines using Alpine-based runner images encounter failures during PDF generation tests. GitLab CI, GitHub Actions, and CircleCI environments using Alpine containers produce inconsistent test results as Chromium versions change.

Serverless and edge deployments where image size directly impacts cold start times prefer Alpine. AWS Lambda layers, Cloudflare Workers, and Azure Functions with container support all benefit from smaller images.

PuppeteerSharp users on .NET face compounded difficulties. The .NET SDK Alpine images (mcr.microsoft.com/dotnet/aspnet:8.0-alpine) combine .NET's managed runtime with Alpine's musl incompatibility. Error messages span both the .NET and native Chromium layers, making diagnosis difficult.

ARM64 architecture deployments add another compatibility layer. Developers running Docker on Apple Silicon Macs (M1/M2/M3) or ARM-based cloud instances cannot use x64 Chromium binaries, and ARM64 Alpine Chromium packages have their own version limitations.

Evidence from the Developer Community

The incompatibility between Puppeteer and Alpine Linux is extensively documented across GitHub issues, community forums, and technical blogs.

Timeline

Date Event Source
2017-10-13 First Alpine deployment issue reported GitHub Issue #379
2018-05-28 Symbol resolution errors documented GitHub Issue #1793
2018-09-24 hb_font_set_variations symbol error pattern identified GitHub Issue #3019
2019-08-07 Alpine edge chromium incompatibility confirmed GitHub Issue #4990
2021-06-15 Mac M1 Alpine emulation failures reported GitHub Issue #7746
2024-03-20 Alpine 3.20 Chrome 123 timeout regressions GitHub Issue #12189

Community Reports

"Chrome does not support Alpine out of the box so make sure you have compatible system dependencies installed on Alpine and test the image before using it."
-- Puppeteer Troubleshooting Documentation

"The current Chromium version in Alpine 3.20 is causing timeout issues with Puppeteer. Downgrading to Alpine 3.19 fixes the issue."
-- GitHub Issue #12189, March 2024

"Building the image on archlinux and running chromium-browser --version will result in: Error relocating /usr/lib/chromium/chrome: hb_font_funcs_set_glyph_h_advances_func: symbol not found"
-- GitHub Issue, alpine-chrome repository

"I'm getting a System.ComponentModel.Win32Exception (2): No such file or directory when trying to launch the Chromium process using Docker base image mcr.microsoft.com/dotnet/core/aspnet:2.2-alpine3.9"
-- GitHub Issue #262, PuppeteerSharp repository

Multiple teams have reported spending days troubleshooting Alpine Puppeteer issues before switching to Debian-based images. The time investment in achieving Alpine compatibility often exceeds the operational savings from smaller image sizes.

Root Cause Analysis

The Puppeteer-Alpine incompatibility exists at multiple technical layers, each contributing to the failure modes developers observe.

musl vs glibc Fundamentals: Alpine Linux uses musl libc, a lightweight implementation of the C standard library. Chromium is compiled against glibc on Linux, using glibc-specific system call wrappers, thread local storage implementations, and dynamic linking behavior. While musl aims for POSIX compatibility, binary compatibility with glibc is explicitly not a goal. Chromium binaries compiled for glibc will not run on musl systems.

Shared Library Differences: Beyond the core C library, Chromium depends on dozens of shared libraries: NSS for cryptography, Pango and HarfBuzz for text rendering, GTK for UI primitives. Each of these has glibc dependencies and Alpine packages its own musl-compiled versions. Version mismatches between the Chromium binary's expectations and the available Alpine packages cause symbol resolution failures.

Package Repository Timing: Alpine's package repositories update on their own schedule. When Alpine 3.20 updated Chromium to version 123, the update broke existing Puppeteer deployments. The Chromium version and Puppeteer version must be compatible, but Alpine's package updates have no coordination with Puppeteer's release cycle.

DevTools Protocol Version Matching: Puppeteer communicates with Chromium via the Chrome DevTools Protocol over WebSockets. The protocol evolves with Chrome versions, and Puppeteer versions target specific Chrome versions. Alpine's Chromium package may implement a different protocol version than Puppeteer expects, causing method calls to fail or timeout.

Font and Rendering Dependencies: Chromium requires system fonts for text rendering. Alpine's minimal installation includes no fonts by default. Missing fonts cause rendering failures, blank PDFs, and crashes with messages about missing font families.

Attempted Workarounds

The developer community has documented extensive workarounds for running Puppeteer on Alpine, each with significant limitations.

Workaround 1: Use Alpine's Chromium Package

Approach: Instead of letting Puppeteer download its bundled Chrome, install Chromium from Alpine's package repository and configure Puppeteer to use it.

FROM node:20-alpine

# Install Chromium and dependencies from Alpine packages
RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont

# Tell Puppeteer to skip Chrome download
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Create non-root user for sandbox
RUN addgroup -S pptruser && adduser -S -G pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser /app

USER pptruser
WORKDIR /app

# Install puppeteer-core to avoid automatic Chrome download
RUN npm install puppeteer-core
Enter fullscreen mode Exit fullscreen mode

JavaScript Launch Configuration:

const puppeteer = require('puppeteer-core');

const browser = await puppeteer.launch({
    executablePath: '/usr/bin/chromium-browser',
    headless: 'new',
    args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu'
    ]
});
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Alpine's Chromium version may not match Puppeteer's expected version
  • Breaking changes occur when Alpine updates Chromium packages
  • Requires manual version coordination between Puppeteer and Alpine Chromium
  • Some DevTools Protocol features may be missing or different

Workaround 2: Pin Alpine and Package Versions

Approach: Lock Alpine version and use specific package repository versions to maintain compatibility.

FROM node:20-alpine3.19

# Use specific Alpine repository version for consistent packages
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.19/community" >> /etc/apk/repositories

# Pin chromium to a known-working version
RUN apk add --no-cache \
    chromium=121.0.6167.85-r0 \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    font-noto-emoji \
    wqy-zenhei

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Requires manual updates when security patches release
  • Specific version strings may not be available across all Alpine versions
  • Creates technical debt as pinned versions age
  • No automatic security updates for the pinned Chromium version

Workaround 3: Switch to Debian-based Images

Approach: Abandon Alpine and use Debian or Ubuntu-based images where glibc compatibility exists.

FROM node:20-slim

# Install Chrome dependencies on Debian
RUN apt-get update && apt-get install -y \
    chromium \
    fonts-liberation \
    libasound2 \
    libatk-bridge2.0-0 \
    libatk1.0-0 \
    libatspi2.0-0 \
    libcups2 \
    libdbus-1-3 \
    libdrm2 \
    libgbm1 \
    libgtk-3-0 \
    libnspr4 \
    libnss3 \
    libxcomposite1 \
    libxdamage1 \
    libxfixes3 \
    libxkbcommon0 \
    libxrandr2 \
    xdg-utils \
    && rm -rf /var/lib/apt/lists/*

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Image size increases from ~500MB to ~1.5GB with Chromium
  • Loses Alpine's memory efficiency benefits
  • Requires re-architecting deployment if Alpine was chosen for specific reasons
  • Still requires version management between Puppeteer and system Chromium

Workaround 4: PuppeteerSharp on Alpine with External Chromium

Approach: For .NET applications, configure PuppeteerSharp to use Alpine's system Chromium.

FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine

RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    libstdc++ \
    font-noto-emoji

ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
Enter fullscreen mode Exit fullscreen mode
using PuppeteerSharp;

var options = new LaunchOptions
{
    Headless = true,
    ExecutablePath = "/usr/bin/chromium-browser",
    Args = new[]
    {
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage",
        "--disable-gpu"
    }
};

await using var browser = await Puppeteer.LaunchAsync(options);
await using var page = await browser.NewPageAsync();
await page.SetContentAsync("<html><body><h1>Test</h1></body></html>");
var pdfBytes = await page.PdfDataAsync();
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • PuppeteerSharp version must be compatible with Alpine's Chromium version
  • The --no-sandbox flag reduces security isolation
  • Chrome crashpad handler errors may appear in logs
  • Zombie process cleanup requires additional configuration

A Different Approach: IronPDF

For .NET developers struggling with Puppeteer Alpine compatibility, IronPDF provides an alternative that avoids the musl-glibc incompatibility entirely. Rather than spawning an external Chromium process that must be compiled for the host system's C library, IronPDF embeds the Chrome rendering engine within the .NET application.

Why IronPDF Handles Alpine Differently

IronPDF acknowledges that direct execution on Alpine Linux is unsupported due to the musl library incompatibility. Instead of attempting workarounds that may break with each Alpine update, IronPDF provides a supported architecture: the IronPdfEngine Docker container.

The IronPdfEngine container runs on a glibc-based Linux distribution internally, handling all Chrome rendering operations. The .NET application connects to this container via gRPC, sending HTML content and receiving rendered PDFs. The application itself can run on any platform, including Alpine, because it only needs network connectivity to the rendering service.

This architecture separates concerns cleanly. The rendering engine runs in an environment where Chromium works reliably, while the application runs wherever .NET supports. Container orchestration handles the communication between services, and the rendering service can scale independently of the application.

Code Example

using IronPdf;
using System;
using System.Threading.Tasks;

// Configure IronPDF to use remote IronPdfEngine container
// This allows the application to run on Alpine while rendering happens
// in a glibc-compatible container
public class AlpineCompatiblePdfService
{
    public AlpineCompatiblePdfService()
    {
        // Point IronPDF to the IronPdfEngine container
        // The engine handles Chrome rendering in a compatible environment
        IronPdf.Installation.ConnectToIronPdfHost("ironpdfengine:33350");
    }

    public async Task<byte[]> GeneratePdfAsync(string htmlContent)
    {
        // Standard IronPDF API - works identically whether local or remote
        var renderer = new ChromePdfRenderer();

        // Configure rendering options
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
        renderer.RenderingOptions.MarginTop = 20;
        renderer.RenderingOptions.MarginBottom = 20;

        // Render HTML to PDF - the engine container handles the Chrome work
        var pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);

        return pdf.BinaryData;
    }
}

// Docker Compose configuration for Alpine application with IronPdfEngine
// docker-compose.yml:
// services:
//   app:
//     build: .
//     depends_on:
//       - ironpdfengine
//   ironpdfengine:
//     image: ironsoftwareofficial/ironpdfengine:latest
//     ports:
//       - "33350:33350"
Enter fullscreen mode Exit fullscreen mode

Dockerfile for Alpine .NET application:

FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .

# No Chromium installation required - rendering happens in IronPdfEngine container
# Application stays minimal and Alpine-native

ENTRYPOINT ["dotnet", "YourApp.dll"]
Enter fullscreen mode Exit fullscreen mode

Key points about this approach:

  • The application container remains small and Alpine-native
  • No Chromium dependencies to manage in the application container
  • Chrome version compatibility is handled by the IronPdfEngine container
  • The rendering service can be scaled independently of the application
  • Standard .NET memory management applies to the application

API Reference

For more details on the methods used:

Migration Considerations

Licensing

IronPDF is commercial software with per-developer licensing. A free trial is available for evaluation. The cost should be weighed against developer time spent maintaining Puppeteer Alpine workarounds and the operational cost of larger Debian-based containers if that path is chosen instead.

Architecture Change

Moving from Puppeteer's embedded browser to IronPDF's remote engine changes the deployment architecture. Instead of a single container with application and browser, two containers communicate over the network. This adds a service dependency but provides cleaner separation of concerns.

API Differences

Puppeteer exposes low-level browser automation primitives. IronPDF provides higher-level PDF-focused APIs:

  • Puppeteer's page.setContent() and page.pdf() becomes renderer.RenderHtmlAsPdf()
  • Browser lifecycle management code can be removed entirely
  • Launch options and Chrome flags are handled by the IronPdfEngine container

What You Gain

  • Elimination of musl-glibc compatibility issues
  • No more Alpine version pinning or Chromium package coordination
  • Consistent behavior across Alpine updates
  • Smaller application container (no Chromium installation)
  • Independent scaling of rendering service

What to Consider

  • Network latency between application and rendering containers
  • Additional container to deploy and manage
  • IronPdfEngine container has its own resource requirements
  • Different architecture pattern from embedded browser approach

Conclusion

The Puppeteer Docker Alpine Chromium compatibility problem stems from fundamental differences between musl and glibc that cannot be resolved through configuration alone. While workarounds exist, they require ongoing maintenance as Alpine and Chromium versions evolve independently.

For .NET developers prioritizing deployment simplicity over architectural purity, IronPDF's remote rendering approach eliminates the compatibility problem by running Chrome where it works reliably while allowing the application to remain on Alpine.


Written by Jacob Mellor, who pioneered C# PDF technology and serves as CTO at Iron Software.


References

  1. Puppeteer Troubleshooting - Running on Alpine{:rel="nofollow"} - Official documentation on Alpine compatibility
  2. GitHub Issue #12189: Alpine ProtocolError after Chrome 123 Update{:rel="nofollow"} - Alpine 3.20 timeout regressions
  3. GitHub Issue #1793: Docker Alpine with Node.js and Chromium Headless{:rel="nofollow"} - Original Alpine compatibility discussion
  4. GitHub Issue #3019: hb_font_set_variations symbol not found{:rel="nofollow"} - HarfBuzz symbol resolution errors
  5. GitHub Issue #379: Issue when deploying with node:8-alpine{:rel="nofollow"} - Early Alpine deployment problems
  6. GitHub Issue #7746: Running Puppeteer on Docker Alpine on Mac failed{:rel="nofollow"} - ARM64 and Apple Silicon issues
  7. GitHub Issue #262: Can't run Puppeteer Sharp in Docker{:rel="nofollow"} - PuppeteerSharp Alpine compatibility
  8. GitHub Issue #9386: libnss3.so cannot open shared object file{:rel="nofollow"} - Missing shared library errors
  9. Chromium Issue #40244829: Compile and link with musl-libc{:rel="nofollow"} - Chromium's official musl support status
  10. How to use Puppeteer inside a Docker container - DEV Community{:rel="nofollow"} - Community guide comparing Alpine vs Debian
  11. IronPDF Docker Linux Documentation - Container deployment guide
  12. IronPdfEngine Docker Documentation - Remote rendering setup

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

Top comments (0)