Repository navigation
ng build deadlocks in Docker on Linux since 21.2.14 — esbuild.context() for production builds breaks standard CI/CD #33480
Description
Which @angular/* package(s) are the source of the bug?
@angular/build
Is this a regression?
Yes — introduced in 21.2.14 by commit `d46c082fb` ("fix(@angular/build): prevent esbuild service child process leakage")
Description
`ng build --configuration=production` deadlocks permanently inside Docker on Linux after 8–20 minutes. No error, no output, process never exits. This breaks every standard Docker-based CI/CD pipeline.
Root cause — confirmed by source diff between 21.2.12 and 21.2.14:
`src/tools/esbuild/bundler-context.js`, non-incremental build path changed from:
// 21.2.12 — correct for production: one-shot, no persistent process
result = await esbuild_1.build(this.#esbuildOptions);to:
// 21.2.14+ — wrong for production: persistent child process + IPC pipe
this.#esbuildContext = await esbuild_1.context(this.#esbuildOptions);
result = await this.#esbuildContext.rebuild();Why this deadlocks:
`esbuild.context()` spawns a persistent child process communicating via stdin/stdout IPC pipe. Angular's TypeScript compiler uses worker threads that call `Atomics.wait()` on a SharedArrayBuffer, blocking until the main thread signals completion. The main thread awaits the esbuild IPC response. Inside Docker on Linux, pipe buffering behaviour differs from the host — the esbuild child blocks writing to a pipe that nobody reads. Circular deadlock. All threads sleep permanently.
Observed state during deadlock:
- All 11 Node.js worker threads: `State: S`, `wchan: 0` (userspace `Atomics.wait()`)
- esbuild child process: 0.4% CPU (idle ping only)
- CPU ticks delta over 5 seconds: 0 — confirmed deadlock, not a slow build
Why this change is wrong for production builds:
The commit message states the leakage "primarily impacts programmatic usage and custom Architect decorators, where the hosting Node.js process remains alive after build completion."
That is precisely not what `ng build` is. A production build runs once and exits. There is nothing to leak. `esbuild.build()` was always correct for non-incremental builds.
Switching non-incremental production builds to `esbuild.context()` + `.rebuild()` to fix a leakage that doesn't exist in standard `ng build` usage introduced a critical regression for Docker/CI environments — which is the majority of production Angular build infrastructure.
Impact:
Corporate and enterprise environments run Angular builds inside Docker containers in CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, Kubernetes build jobs). These environments have standard resource allocations. They cannot and should not need to provision extra CPU and RAM to work around a pipe IPC timing issue introduced by an unnecessary API switch for production builds.
This is a silent failure: no error message, no log output, the build simply hangs. Teams debugging this lose hours with no indication of the cause.
Proposed fix:
Restore `esbuild.build()` for the non-incremental branch in `src/tools/esbuild/bundler-context.js`. The scoped `esbuild.context()` should only be used when `this.incremental` is true (watch mode), which is exactly what 21.2.12 and 21.2.13 did correctly:
if (this.#esbuildContext) {
// Rebuild using the existing incremental build context
result = await this.#esbuildContext.rebuild();
}
else if (this.incremental) {
// Create an incremental build context and perform the first build.
this.#esbuildContext = await esbuild_1.context(this.#esbuildOptions);
result = await this.#esbuildContext.rebuild();
}
else {
// For non-incremental builds, perform a single build — esbuild.build() is correct here.
// esbuild.context() creates a persistent IPC pipe that deadlocks in Docker on Linux.
result = await esbuild_1.build(this.#esbuildOptions);
}If the child process leakage needs fixing for non-incremental builds too, it should be addressed within the `esbuild.build()` path (e.g. explicit service stop after completion) — not by switching to `esbuild.context()` which introduces IPC timing sensitivity.
Request:
Either revert the non-incremental build path to `esbuild.build()`, or provide a technical justification for why `esbuild.context()` is necessary for non-incremental production builds and how Docker environments are expected to handle the resulting IPC deadlock.
Environment
Node: 22.x
Docker: Linux amd64 (node:22-slim)
@angular/build: 21.2.14 through 21.2.17 (all affected)
Confirmed working: @angular/build 21.2.12 and 21.2.13
OS inside container: Debian (slim) / Alpine
Activity
alan-agius4 commentedon Jun 29, 2026
I'm sorry, but we can't reproduce the problem following the instructions you provided.
Remember that we have a large number of issues to resolve, and have only a limited amount of time to reproduce your issue.
Short, explicit instructions make it much more likely we'll be able to reproduce the problem so we can fix it.
If the problem persists, please open a new issue following our submission guidelines.
A good way to make a minimal repro is to create a new app via
ng new repro-appand add the minimum possible code to show the problem. Then you can push this repository to github and link it here.hskiba commentedon Jul 2, 2026
We were experiencing this in our project as well. As requested, I've created a new issue #33497 that references a GitHub repository with a minimal repro.
isms-core-project commentedon Jul 2, 2026
Glad to hear that I am not the only one having the issue.
angular-automatic-lock-bot commentedon Aug 2, 2026
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.