Python 3.10 end of life is Oct 31: find every pinned Python and Node version
Python 3.10 reaches end of life on October 31, 2026. Node 20 already did, on April 30, 2026. End of life doesn't break anything. The interpreter keeps running. What stops is security releases: the next CVE in that version never gets a patch, and nothing in your build turns red to tell you. The hard part isn't upgrading. It's knowing where the version is pinned, because it's rarely in one place. Where runtime versions hide File What it looks like Dockerfile FROM python:3.10-slim, FROM node:20-alpine .python-version 3.10 or 3.10.14 runtime.txt python-3.10.14 (Heroku-style platforms) .nvmrc / .node-version 20 serverless.yml runtime: python3.10, runtime: nodejs20.x pyproject.toml requires-python = ">=3.10,=3.8" isn't a pin, so it doesn't count.) Whatever you use, get the dates on a calendar before they pass.
Python 3.10 reaches end of life on October 31, 2026. Node 20 already did, on April 30, 2026.
End of life doesn't break anything. The interpreter keeps running. What stops is security releases: the next CVE in that version never gets a patch, and nothing in your build turns red to tell you.
The hard part isn't upgrading. It's knowing where the version is pinned, because it's rarely in one place.
Where runtime versions hide
| File | What it looks like |
|---|---|
Dockerfile |
FROM python:3.10-slim, FROM node:20-alpine
|
.python-version |
3.10 or 3.10.14
|
runtime.txt |
python-3.10.14 (Heroku-style platforms) |
.nvmrc / .node-version
|
20 |
serverless.yml |
runtime: python3.10, runtime: nodejs20.x
|
pyproject.toml |
requires-python = ">=3.10,<3.11" |
package.json |
"engines": { "node": "20.x" } |
| CI config |
python-version: "3.10", node-version: 20
|
A quick audit script
Run this from the root of a repository:
#!/usr/bin/env bash
# Find pinned Python 3.10 and Node 20 runtimes
grep -RInE \
--include=Dockerfile* --include=.python-version --include=runtime.txt \
--include=.nvmrc --include=.node-version --include=serverless.y*ml \
--include=pyproject.toml --include=package.json --include=*.yml --include=*.yaml \
-e 'python:?3\.10' -e 'python-?3\.10' -e 'python3\.10' -e '"3\.10' \
-e 'node:20' -e 'nodejs20' -e '^20(\.|$)' -e 'node-version: *"?20' \
. 2>/dev/null | grep -v node_modules
It's noisy on purpose. A false positive costs you two seconds. A missed FROM python:3.10 costs you a year of unpatched base images.
What to move to
Check endoflife.date before you pick. As of today:
- Python: 3.10 ends Oct 31, 2026. Move to a version with years of support left, not the next one up.
- Node: 20 ended Apr 30, 2026. Node 22 is supported until Apr 30, 2027, so a move to 22 buys you six months. Plan for the one after it at the same time.
Make it someone's job (or something's)
The audit above works once. The dates keep coming, though: there's a runtime end of life every few months across Python, Node, Ruby, Go and Java.
That's why we made it a daily check in Versyka, for Python and Node.js. It reads .python-version, .nvmrc and .node-version, runtime.txt, your Dockerfiles, and the runtime in serverless.yml or a SAM template.yaml. Then it checks each pinned version against endoflife.date and warns you 180 days before the date, by email, Slack and on the dashboard. It alerts only: changing a base image is a decision you should make, not a bot. (A minimum like requires-python = ">=3.8" isn't a pin, so it doesn't count.)
Whatever you use, get the dates on a calendar before they pass.