We upgraded a service from ASP.NET 8 to 10. No application code changed—but our Puppeteer-based document pipeline completely broke.
The root cause wasn’t .NET. It was a subtle base image change:
Debian → Ubuntu, which replaced a working Chromium install with a non-functional Snap wrapper inside Docker.
The Setup
Our document generation pipeline works like this:
Render HTML using Puppeteer
Use Chromium as the headless browser
Post-process assets with Sharp
Run everything inside a Docker container
This setup worked reliably on ASP.NET 8.
What Changed?
As part of the upgrade:
From:
mcr.microsoft.com/dotnet/aspnet:8.0(Debian-based)To:
mcr.microsoft.com/dotnet/aspnet:10.0(Ubuntu-based)
At first glance, nothing looked risky. But this OS-level change had a critical side effect.
The Real Problem (Root Cause)
On Debian:
-
apt install chromiuminstalls a real Chromium binary
On Ubuntu:
-
apt install chromiuminstalls a Snap wrapper, not the browser itself
That distinction is the entire problem.
Why This Breaks in Docker
Snap packages are not just alternative installers—they rely on a runtime model that assumes:
a full init system (
systemd)background services (
snapd)elevated privileges
Docker containers deliberately avoid all of these.
So while apt install chromium appears successful, it installs a deferred execution wrapper, not an actual browser.
When Puppeteer tries to launch Chromium, there is nothing usable to execute.
Symptoms (and Why They’re Misleading)
Installation appears successful:
apt install chromium
But Puppeteer fails with:
Failed to launch the browser process!
On inspection:
which chromium
# /usr/bin/chromium-browser
file /usr/bin/chromium-browser
# POSIX shell script, not a binary
This is the key signal:
Chromium is not actually installed—only a wrapper script exists.
What We Tried (That Didn’t Work)
These approaches all failed:
Installing additional libraries (
libx11,libnspr4, fonts, etc.)Switching Puppeteer executable paths
Using snap install chromium
Installing from default Ubuntu repositories
Using Puppeteer’s bundled Chromium (in our case)
All of these fail for the same reason:
Snap is fundamentally incompatible with standard Docker containers.
The Fix
The solution is to avoid Ubuntu’s Snap-based Chromium entirely and install Chromium from a non-Snap APT source that provides a real binary.
Once we did this:
Chromium installed correctly
/usr/bin/chromium existed as a real executable
Puppeteer launched successfully
Example Dockerfile
# Install Chromium from a non-Snap source
RUN add-apt-repository ppa:xtradeb/apps -y \
&& apt update \
&& apt install -y \
chromium \
fonts-liberation \
libx11-xcb1 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libgbm1 \
libasound2 \
libnspr4 \
libnss3 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
Puppeteer configuration:
ExecutablePath = "/usr/bin/chromium",
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox"
};
Alternative Approaches (Tradeoffs)
There are a few ways to solve this problem:
- Use a non-Snap APT source (what we did)
* Keeps Ubuntu base image
* Requires managing external repo
- Switch back to Debian-based images
* Simpler dependency model
* Diverge from official runtime images
- Use Puppeteer’s bundled Chromium
* Less setup
* Larger image size, less control
We chose option 1 to keep compatibility while maintaining control over dependencies.
Key Insight
This issue is not specific to Puppeteer or ASP.NET.
Any headless browser setup (Puppeteer, Playwright, Selenium) running inside Ubuntu-based Docker containers can hit this failure mode.
The underlying problem is a mismatch between:
Snap’s system-level assumptions
Docker’s minimal runtime model
Lessons Learned
-
Base Images Are Part of Your Runtime Contract
Framework upgrades can silently change your OS layer. Treat base images as a first-class dependency.
-
“Installed” Does Not Mean “Runnable”
Package managers can install indirections instead of binaries. Always verify the actual executable:
file $(which chromium) -
Snap and Containers Don’t Mix
Snap packages are not designed for containerized environments. Avoid them in production Docker setups.
-
Validate Critical Dependencies Early
If your system depends on external binaries (like browsers), validate them explicitly after upgrades—not after deployment failures.
Final Thoughts
This issue took longer to diagnose than expected because:
Installation appeared successful
Errors were misleading
The OS-level change wasn’t obvious
If you’re upgrading to ASP.NET 10 and rely on headless browsers, validate your Chromium setup early.
The failure mode is silent—and easy to miss.
Top comments (0)