<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pankaj Sharma</title>
    <description>The latest articles on DEV Community by Pankaj Sharma (@pankajsharmadev).</description>
    <link>https://dev.to/pankajsharmadev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3879734%2F74d5d006-6f4e-42a8-a544-c287c9c78f02.png</url>
      <title>DEV Community: Pankaj Sharma</title>
      <link>https://dev.to/pankajsharmadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pankajsharmadev"/>
    <language>en</language>
    <item>
      <title>When “Works on Lambda” Breaks on ECS: A Concurrency Bug from Misusing Singletons in .NET</title>
      <dc:creator>Pankaj Sharma</dc:creator>
      <pubDate>Fri, 01 May 2026 08:07:22 +0000</pubDate>
      <link>https://dev.to/pankajsharmadev/when-works-on-lambda-breaks-on-ecs-a-concurrency-bug-from-misusing-singletons-in-net-2ia2</link>
      <guid>https://dev.to/pankajsharmadev/when-works-on-lambda-breaks-on-ecs-a-concurrency-bug-from-misusing-singletons-in-net-2ia2</guid>
      <description>&lt;p&gt;We recently migrated a .NET Core application from .NET 8 to .NET 10 and moved its hosting model from AWS Lambda to ECS. The service acts as a wrapper around a downstream API that reads and updates client “fact find” data.&lt;/p&gt;

&lt;p&gt;Post-migration, we started seeing critical issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users were seeing other users’ data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updates were being applied to the wrong clients&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Users could access data they shouldn’t have permission to see&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a data isolation failure. The root cause turned out to be a misuse of a singleton combined with differences in execution models between Lambda and ECS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Context
&lt;/h2&gt;

&lt;p&gt;The service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Accepts user requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds headers (auth/user context)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calls a downstream API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returns processed responses&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A shared configuration object (&lt;code&gt;ConnectionOptions&lt;/code&gt;) was introduced to manage headers for outbound calls.&lt;/p&gt;

&lt;p&gt;This object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Was registered as a &lt;strong&gt;singleton&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Was &lt;strong&gt;mutated per request&lt;/strong&gt; to inject headers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why It “Worked” on Lambda
&lt;/h2&gt;

&lt;p&gt;AWS Lambda has a different execution model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each invocation typically runs in isolation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even with warm starts, concurrency per instance is limited&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No true parallel execution within a single instance (by default)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mutating a singleton per request &lt;em&gt;appears safe&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency issues are masked&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flawed design went unnoticed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in ECS
&lt;/h2&gt;

&lt;p&gt;ECS runs your app as a long-lived service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Multiple requests are processed &lt;strong&gt;concurrently&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Threads share the same memory space&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Singleton services are shared across all requests&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConnectionOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConnectionOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;connectionOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Two requests arrive at the same time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request A&lt;/th&gt;
&lt;th&gt;Request B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sets header to User A&lt;/td&gt;
&lt;td&gt;Sets header to User B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calls downstream API&lt;/td&gt;
&lt;td&gt;Calls downstream API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because both share the same singleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Headers overwrite each other&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requests leak state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downstream API gets incorrect user context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cross-user data exposure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Incorrect updates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Non-deterministic failures&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Root Cause
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mutable shared state in a singleton in a concurrent environment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More precisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ConnectionOptions&lt;/code&gt; held request-specific data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It was registered as a singleton&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It was mutated per request&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This violates a core rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Singletons must be stateless or immutable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Is Hard to Catch
&lt;/h2&gt;

&lt;p&gt;This issue didn’t show up in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Local development (low concurrency)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda (isolated execution)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests (typically single-threaded)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It only surfaced under:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Concurrent load&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-threaded execution (ECS)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Use Scoped Lifetime
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConnectionOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each request gets its own instance.&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 2: Make It Immutable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConnectionOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ConnectionOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new instance per request instead of mutating.&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 3: Avoid Shared State (Preferred)
&lt;/h3&gt;

&lt;p&gt;Pass headers explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CallAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpRequestMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes shared mutable state entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Execution Model Matters
&lt;/h3&gt;

&lt;p&gt;Lambda and ECS behave differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lambda hides concurrency issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ECS exposes them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Singleton ≠ Safe
&lt;/h3&gt;

&lt;p&gt;Singletons are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Shared globally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unsafe if mutable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use only for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stateless services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immutable configuration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Don’t Store Request Data in Singletons
&lt;/h3&gt;

&lt;p&gt;If data changes per request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It should not live in a singleton&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Concurrency Bugs Are Non-Deterministic
&lt;/h3&gt;

&lt;p&gt;They:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are hard to reproduce&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Appear random&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Often show up only in production&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;This wasn’t a .NET 10 issue or an ECS issue.&lt;/p&gt;

&lt;p&gt;It was a design flaw that only became visible when the runtime stopped masking it.&lt;/p&gt;

&lt;p&gt;When migrating from Lambda to ECS (or any long-running service model), review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Singleton usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shared mutable state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request-scoped data handling&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where subtle, high-impact bugs tend to hide.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>netcore</category>
      <category>ecs</category>
    </item>
    <item>
      <title>Chromium + Puppeteer Broke After ASP.NET 8 10 Upgrade — The Hidden Snap Trap in Docker</title>
      <dc:creator>Pankaj Sharma</dc:creator>
      <pubDate>Fri, 01 May 2026 08:05:26 +0000</pubDate>
      <link>https://dev.to/pankajsharmadev/chromium-puppeteer-broke-after-aspnet-8-10-upgrade-the-hidden-snap-trap-in-docker-2mpf</link>
      <guid>https://dev.to/pankajsharmadev/chromium-puppeteer-broke-after-aspnet-8-10-upgrade-the-hidden-snap-trap-in-docker-2mpf</guid>
      <description>&lt;p&gt;We upgraded a service from ASP.NET 8 to 10. No application code changed—but our Puppeteer-based document pipeline completely broke.&lt;/p&gt;

&lt;p&gt;The root cause wasn’t .NET. It was a subtle base image change:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Debian → Ubuntu&lt;/strong&gt;, which replaced a working Chromium install with a non-functional Snap wrapper inside Docker.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Our document generation pipeline works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Render HTML using Puppeteer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Chromium as the headless browser&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Post-process assets with Sharp&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run everything inside a Docker container&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup worked reliably on ASP.NET 8.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed?
&lt;/h2&gt;

&lt;p&gt;As part of the upgrade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From: &lt;code&gt;mcr.microsoft.com/dotnet/aspnet:8.0&lt;/code&gt; (Debian-based)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To: &lt;code&gt;mcr.microsoft.com/dotnet/aspnet:10.0&lt;/code&gt; (Ubuntu-based)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first glance, nothing looked risky. But this OS-level change had a critical side effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem (Root Cause)
&lt;/h2&gt;

&lt;p&gt;On Debian:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;apt install chromium&lt;/code&gt; installs a real Chromium binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Ubuntu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;apt install chromium&lt;/code&gt; installs a Snap wrapper, not the browser itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction is the entire problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Breaks in Docker
&lt;/h2&gt;

&lt;p&gt;Snap packages are not just alternative installers—they rely on a runtime model that assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a full init system (&lt;code&gt;systemd&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;background services (&lt;code&gt;snapd&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;elevated privileges&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker containers deliberately avoid all of these.&lt;/p&gt;

&lt;p&gt;So while &lt;code&gt;apt install chromium&lt;/code&gt; appears successful, it installs a &lt;strong&gt;deferred execution wrapper&lt;/strong&gt;, not an actual browser.&lt;/p&gt;

&lt;p&gt;When Puppeteer tries to launch Chromium, there is nothing usable to execute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Symptoms (and Why They’re Misleading)
&lt;/h2&gt;

&lt;p&gt;Installation appears successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Puppeteer fails with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to launch the browser process!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On inspection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which chromium
&lt;span class="c"&gt;# /usr/bin/chromium-browser&lt;/span&gt;

file /usr/bin/chromium-browser
&lt;span class="c"&gt;# POSIX shell script, not a binary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key signal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Chromium is not actually installed—only a wrapper script exists.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What We Tried (That Didn’t Work)
&lt;/h2&gt;

&lt;p&gt;These approaches all failed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installing additional libraries (&lt;code&gt;libx11&lt;/code&gt;, &lt;code&gt;libnspr4&lt;/code&gt;, fonts, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switching Puppeteer executable paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using snap install chromium&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Installing from default Ubuntu repositories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Puppeteer’s bundled Chromium (in our case)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these fail for the same reason:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Snap is fundamentally incompatible with standard Docker containers.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The solution is to &lt;strong&gt;avoid Ubuntu’s Snap-based Chromium entirely&lt;/strong&gt; and install Chromium from a non-Snap APT source that provides a real binary.&lt;/p&gt;

&lt;p&gt;Once we did this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Chromium installed correctly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/usr/bin/chromium existed as a real executable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppeteer launched successfully&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Chromium from a non-Snap source&lt;/span&gt;
RUN add-apt-repository ppa:xtradeb/apps &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt update &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       chromium &lt;span class="se"&gt;\&lt;/span&gt;
       fonts-liberation &lt;span class="se"&gt;\&lt;/span&gt;
       libx11-xcb1 &lt;span class="se"&gt;\&lt;/span&gt;
       libxcomposite1 &lt;span class="se"&gt;\&lt;/span&gt;
       libxdamage1 &lt;span class="se"&gt;\&lt;/span&gt;
       libxrandr2 &lt;span class="se"&gt;\&lt;/span&gt;
       libgbm1 &lt;span class="se"&gt;\&lt;/span&gt;
       libasound2 &lt;span class="se"&gt;\&lt;/span&gt;
       libnspr4 &lt;span class="se"&gt;\&lt;/span&gt;
       libnss3 &lt;span class="se"&gt;\&lt;/span&gt;
       ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puppeteer configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ExecutablePath &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/bin/chromium"&lt;/span&gt;,
Args &lt;span class="o"&gt;=&lt;/span&gt; new[]
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"--no-sandbox"&lt;/span&gt;,
    &lt;span class="s2"&gt;"--disable-setuid-sandbox"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Alternative Approaches (Tradeoffs)
&lt;/h2&gt;

&lt;p&gt;There are a few ways to solve this problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Use a non-Snap APT source (what we did)&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Keeps Ubuntu base image

*   Requires managing external repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; Switch back to Debian-based images&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Simpler dependency model

*   Diverge from official runtime images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; Use Puppeteer’s bundled Chromium&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Less setup

*   Larger image size, less control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We chose option 1 to keep compatibility while maintaining control over dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Insight
&lt;/h2&gt;

&lt;p&gt;This issue is not specific to Puppeteer or ASP.NET.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any headless browser setup (Puppeteer, Playwright, Selenium) running inside Ubuntu-based Docker containers can hit this failure mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The underlying problem is a mismatch between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Snap’s system-level assumptions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker’s minimal runtime model&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Base Images Are Part of Your Runtime Contract&lt;/p&gt;

&lt;p&gt;Framework upgrades can silently change your OS layer. Treat base images as a first-class dependency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“Installed” Does Not Mean “Runnable”&lt;/p&gt;

&lt;p&gt;Package managers can install indirections instead of binaries. Always verify the actual executable:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;file &lt;span class="si"&gt;$(&lt;/span&gt;which chromium&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Snap and Containers Don’t Mix&lt;/p&gt;

&lt;p&gt;Snap packages are not designed for containerized environments. Avoid them in production Docker setups.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validate Critical Dependencies Early&lt;/p&gt;

&lt;p&gt;If your system depends on external binaries (like browsers), validate them explicitly after upgrades—not after deployment failures.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This issue took longer to diagnose than expected because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installation appeared successful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Errors were misleading&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The OS-level change wasn’t obvious&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re upgrading to ASP.NET 10 and rely on headless browsers, validate your Chromium setup early.&lt;/p&gt;

&lt;p&gt;The failure mode is silent—and easy to miss.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Hello World — Building and Learning in Public</title>
      <dc:creator>Pankaj Sharma</dc:creator>
      <pubDate>Fri, 01 May 2026 08:04:18 +0000</pubDate>
      <link>https://dev.to/pankajsharmadev/hello-world-building-and-learning-in-public-4d3m</link>
      <guid>https://dev.to/pankajsharmadev/hello-world-building-and-learning-in-public-4d3m</guid>
      <description>&lt;p&gt;Hello World — Building and Learning in Public&lt;/p&gt;

&lt;p&gt;I work on backend systems, mostly in the .NET ecosystem, trying to understand how systems behave under scale, failures, and real-world constraints.&lt;/p&gt;

&lt;p&gt;This blog is where I document that process in public.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’ll Write About
&lt;/h2&gt;

&lt;p&gt;Real production issues and debugging Challenges we face while building systems (and how we deal with them) System design trade-offs .NET internals and performance&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Exists
&lt;/h2&gt;

&lt;p&gt;I’m learning in public.&lt;/p&gt;

&lt;p&gt;Most posts will come from real problems, mistakes, and things that didn’t work the first time. Writing helps me think clearly and refine my understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;If you’re trying to move beyond just shipping code to actually understanding systems, this might be useful.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>buildinpublic</category>
      <category>dotnet</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
