Get your API key
Node.js 26 goes LTS in October 2026: what changes for your PDF Docker image

Node.js 26 goes LTS in October 2026: what changes for your PDF Docker image

Node 26 enters LTS on 28 October 2026 and Node 27 starts a one-major-per-year schedule. What it means for a headless Chromium PDF container.

11 min read

Node.js 26 becomes Active LTS on 28 October 2026, and it is the last major released under the even/odd model that has governed Node.js since 2015. Starting with Node.js 27, Node.js ships one major per year, every major becomes an LTS line, and a six-month alpha channel opens ahead of each April release. If you run a container that renders PDFs with headless Chromium, this changes the rhythm of your base image upgrades more than it changes your code.

This article covers what is actually confirmed, the dates that matter for a rendering container, and a Docker tagging strategy that survives the transition. For the container fundamentals (system libraries, fonts, zombie processes), see PDF generation in Docker.

What exactly is changing in the Node.js release schedule?

Node.js moves from two majors per year to one, starting in October 2026 with the Node.js 27 line. The official announcement lays out three concrete changes.

First, the even/odd split disappears. Under the old model, even-numbered majors landed in April and were promoted to LTS in October, while odd-numbered majors landed in October and never became LTS. Under the new model, every major is released in April and promoted to LTS the following October. There is no longer a class of releases you are told to skip in production.

Second, a six-month alpha phase runs from October to March before each April release. Semver-major changes are allowed during alpha, which gives library authors and container maintainers a window to test breakage before the version is called Current.

Third, version numbers align with the calendar year: 27.0.0 in 2027, 28.0.0 in 2028. The stated motivation is reducing the number of concurrent release lines the volunteer release team has to maintain.

LTS duration is not shrinking. The announcement keeps LTS at roughly 30 months, for 36 months total from first Current release to end-of-life, the same window a supported even-numbered line gets today.

When does Node.js 26 become LTS, and when does Node 22 die?

Here is the support calendar for every line a PDF container might plausibly sit on, from the nodejs/Release schedule.

VersionInitial releaseActive LTSMaintenanceEnd-of-life
20.x (Iron)18 Apr 202324 Oct 202322 Oct 202430 Apr 2026
22.x (Jod)24 Apr 202429 Oct 202421 Oct 202530 Apr 2027
24.x (Krypton)6 May 202528 Oct 202520 Oct 202630 Apr 2028
26.x5 May 202628 Oct 202620 Oct 202730 Apr 2029
27.xApr 2027Oct 2027not publishedApr 2030

Three dates matter if you are planning container work this quarter.

Node 20 is already dead. It reached end-of-life on 30 April 2026, which means no more security patches, including for the OpenSSL and HTTP parsing surface that a rendering service exposes. A node:20-slim base image is now an unpatched base image.

Node 24 enters maintenance on 20 October 2026, eight days before Node 26 goes LTS. Maintenance means critical fixes only. A line in maintenance is still a legitimate production target, it just stops receiving non-critical backports.

Node 22 reaches end-of-life on 30 April 2027. That is the real deadline for anyone still on node:22-slim, and it is close enough that a Node 22 container built today has about seven months of support left.

What does Node.js 26 actually ship?

Node.js 26.0.0 was released on 5 May 2026 with V8 14.6, the engine version that ships in Chromium 146. The release notes list a handful of changes that reach a PDF pipeline.

Temporal is enabled by default. The replacement date and time API is no longer behind a flag, which matters for PDF generation specifically because invoice and report templates are full of date formatting. Temporal gives you a proper PlainDate and time zone handling without pulling in a date library, though Handlebars helpers and template code that call Date keep working unchanged.

Undici 8 is bundled, so fetch and the HTTP client behave differently in edge cases. If your render path fetches remote images or Google Fonts before handing HTML to Chromium, this is the piece most likely to surface a behavior change.

The removals are the part worth auditing. http.Server.prototype.writeHeader() is gone in favor of writeHead(). The legacy internal stream modules (_stream_readable, _stream_writable, _stream_wrap, and the rest) are removed. The --experimental-transform-types flag is removed, which breaks any Dockerfile that passes it to run TypeScript directly. module.register() is runtime deprecated, not removed, but it now warns.

Build requirements moved too: GCC 13.2 is the new floor, Python 3.9 support is dropped, and the Windows SDK requirement went to 11. This affects you indirectly. If a native dependency in your image compiles from source instead of pulling a prebuilt binary, the toolchain in your builder stage has to satisfy the newer floor.

Why is a PDF container more version-sensitive than a plain API container?

A headless-browser PDF service sits at the intersection of three things that each have their own Node.js opinion, which is why a major bump costs more here than in a stateless JSON API.

Native modules compiled against the Node ABI. Every Node major changes NODE_MODULE_VERSION. Packages with native bindings (SQLite drivers, image codecs, compression libraries) ship prebuilt binaries per ABI. When you move majors before the package publishes a matching prebuild, npm falls back to compiling from source, which means your builder stage suddenly needs python3, make, and g++ and your build time triples. This is the single most common way a Node bump breaks a container that was previously fine.

A browser automation library with its own Node floor. Puppeteer 25 raised its minimum to Node 22.12, up from Node 18 in earlier majors, and its documented policy is to follow the latest maintenance LTS. Playwright lists latest 22.x, 24.x or 26.x under system requirements. A container on an end-of-life Node line cannot install current versions of either, so the Node upgrade becomes a prerequisite for the browser upgrade, not a parallel task.

A Chromium binary of several hundred megabytes. The browser download dominates image size, so the Node base layer looks cheap by comparison. That framing is misleading on the security side: the Node runtime is the part receiving CVE patches on a schedule, and an end-of-life Node line means those patches stop while the Chromium binary keeps updating independently. You end up with a patched browser on an unpatched runtime.

What Docker tag should a PDF image pin to?

Pin to a major LTS tag, never to latest or to a bare version-free image.

node:latest follows whichever major Node.js published most recently. Under the old schedule that meant your container could land on an odd-numbered release that was never going to receive LTS patches. Under the new schedule every major becomes LTS eventually, but latest still moves you across a major boundary on a routine rebuild, with no code change and no review. That is how a CI pipeline silently discovers a native module ABI mismatch at 2am.

The practical ladder, from loosest to tightest:

TagMoves on rebuildUse when
node:latestAny major, any minorNever, in production
node:26Any 26.x patch or minorFine for most services
node:26-slimAny 26.x, Debian slim baseDefault for a PDF container
node:26.1.0-slimNothingReproducible builds, needs a bot to bump
digest pinned (node@sha256:...)Nothing at allCompliance, supply-chain audits

node:26-slim is the right default for a rendering container. It picks up patch-level security fixes on rebuild, stays on a known major, and the slim Debian base is the one whose Chromium dependency package names are documented everywhere. Alpine is tempting for size but Chromium on musl is a different build with its own font and dependency quirks, which is a poor trade for a service whose main job is rendering text correctly.

An example multi-stage Dockerfile pinned to a Node LTS

This is the shape of a PDF rendering image with the Node major declared once and reused, so a migration is a one-line change.

# One place to change the Node major.
ARG NODE_VERSION=26
 
FROM node:${NODE_VERSION}-slim AS deps
WORKDIR /app
# Toolchain for native modules that have no prebuild for this ABI.
RUN apt-get update && apt-get install -y --no-install-recommends \
      python3 make g++ ca-certificates \
    && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci
# Browser goes to a fixed path so the runner does not depend on $HOME.
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright
RUN npx playwright install --with-deps chromium
 
FROM node:${NODE_VERSION}-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
FROM node:${NODE_VERSION}-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright
 
# Chromium runtime libraries only, no compiler in the final image.
RUN apt-get update && apt-get install -y --no-install-recommends \
      libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
      libdbus-1-3 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 \
      libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 \
      fonts-liberation fonts-noto-color-emoji \
    && rm -rf /var/lib/apt/lists/*
 
RUN addgroup --system --gid 1001 nodejs \
 && adduser --system --uid 1001 --ingroup nodejs renderer
 
COPY --from=builder --chown=renderer:nodejs /app/dist ./dist
COPY --from=deps --chown=renderer:nodejs /app/node_modules ./node_modules
COPY --from=deps /opt/ms-playwright /opt/ms-playwright
 
USER renderer
EXPOSE 3000
CMD ["node", "dist/server.js"]

Three details do the work. The ARG NODE_VERSION at the top means a major migration is one edit and a full rebuild, not a search across stages that drift apart. The compiler toolchain lives only in the deps stage, so the final image carries no g++. And PLAYWRIGHT_BROWSERS_PATH is set identically in the stage that downloads Chromium and the stage that runs it, which is the fix for the most common "browser not found" failure after a base image change.

Build the same image against two majors before committing the bump:

docker build --build-arg NODE_VERSION=24 -t pdfsvc:node24 .
docker build --build-arg NODE_VERSION=26 -t pdfsvc:node26 .

If the Node 26 build takes noticeably longer, a native dependency lost its prebuild and is compiling from source. That is the signal to check the package for an ABI-matching release before you ship.

When should you actually migrate to Node 26?

Wait for 28 October 2026, then move deliberately. Running Node 26 while it is still Current buys you nothing in a container that renders PDFs: Temporal and Undici 8 are useful but not urgent, and Current means the line can still take changes you have to react to. Once it is Active LTS, the support window runs to 30 April 2029, which is the longest runway available.

The exception is anyone on Node 20. That line has been unsupported since 30 April 2026, and the fix is not optional. Jumping straight from 20 to 26 in late October is fewer migrations than 20 to 24 now and 24 to 26 later.

Anyone on Node 22 has until 30 April 2027, but the calendar is unkind: Node 22 dies about five months before Node 28 would land under the new annual schedule. Moving to 26 in late 2026 puts you on a line that outlives the next two majors.

The upside of the annual schedule for container maintainers is that a base image bump becomes a once-a-year, October-shaped task instead of a twice-a-year decision about whether this particular major is the one you are allowed to use. The alpha channel from October to March is where the testing happens, so a team that cares about staying current can build against the alpha tag in CI and arrive at the April release already knowing what breaks.

Summary

Node.js 26 goes Active LTS on 28 October 2026 with support through 30 April 2029, and it is the last major under the even/odd model. Node.js 27 opens the annual schedule with an alpha phase starting October 2026 and general availability in April 2027.

For a PDF container the practical checklist is short. Get off Node 20, which has been end-of-life since 30 April 2026. Pin to node:26-slim from late October rather than latest. Declare the major once with a build argument so a migration is a one-line change. Check that your native dependencies publish prebuilt binaries for the new ABI before you bump, because a source rebuild is where the time goes. And remember that Puppeteer 25 already requires Node 22.12, so the browser library and the runtime move together whether you plan it that way or not.

If you would rather not maintain the base image at all, PDF4.dev runs the Chromium container so you can call a REST endpoint instead. The HTML to PDF tool is the same rendering path without the Dockerfile.

Free tools mentioned:

Html To PdfTry it freeWebpage To PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.