Python 3.10 End of Life: A Production Migration Guide

Python 3.10 reached end of life on October 1, 2026. Python 3.10.22 was its final security release. The branch is frozen, no further fixes will be published, and new issues against it are no longer accepted by the Python project.
A production migration is larger than changing FROM python:3.10 to a newer tag. The interpreter appears in CI runners, containers, serverless configuration, scheduled jobs, notebooks, developer tools, native extension wheels, and packaging metadata. A service can pass tests on Python 3.14 while one forgotten worker continues to run 3.10 in production.
This guide moves a Python 3.10 application to a supported release through inventory, dependency resolution, porting work, clean builds, canary deployment, and rollback. The default target is Python 3.14, with Python 3.13 as a documented compatibility step when a critical dependency blocks 3.14.
The short answer
Move production applications from Python 3.10 to Python 3.14 unless a verified platform or dependency constraint requires Python 3.13 temporarily. Do not stop at “the tests pass.” Complete these stages:
- Inventory every place that selects or embeds Python.
- Capture a Python 3.10 production baseline.
- Resolve dependencies from a clean Python 3.14 environment.
- Audit removed modules, deprecations, and native extensions.
- Run unit, integration, contract, load, and startup tests.
- Rebuild containers and deployment artifacts from scratch.
- Canary the new runtime with the same code and configuration.
- Compare errors, latency, CPU, memory, and job outcomes.
- Roll out in stages while the old immutable image remains briefly available.
- Remove 3.10 from CI and deployment policy after the rollback window.
PEP 619 records October 1, 2026 as the end of Python 3.10 support and 3.10.22 as the final security release. Continuing to run the interpreter after that date means accepting unpatched runtime defects and security issues.
Choose the target before changing code
Python 3.14 is the default production target
The Python Developer's Guide version status table lists Python 3.14 in bugfix support with an end-of-life date in October 2030. It offers the longest stable runway among generally available production releases as of October 2026.
Bugfix status matters. The branch still receives regular correctness and security updates, while a security-only branch receives a narrower set of source releases on an irregular schedule.
Python 3.13 is a compatibility step, not the automatic destination
Use 3.13 when a business-critical package, operating system image, managed runtime, or native extension has documented 3.13 support but not 3.14 support. Record the blocker, owner, upstream issue, and deadline to revisit it.
Without that record, a temporary step becomes another aging runtime. Python 3.13 is supported through October 2029, so it is safe as an interim release but provides one less year of runway than 3.14.
Python 3.11 buys too little time
Python 3.11 is in security-only support and reaches end of life in October 2027. Moving a production fleet from 3.10 to 3.11 in late 2026 creates another urgent migration within a year. Choose it only when an external platform makes every newer supported version impossible and the platform migration is already scheduled.
Python 3.12 remains supported through October 2028, but the same logic applies. A direct move to 3.14 is usually a better use of testing effort.
Keep free-threaded Python as a separate decision
Use the standard GIL-enabled CPython build for the end-of-life migration unless the application already has an approved free-threading project. A free-threaded build changes extension compatibility, concurrency behavior, performance assumptions, and the test matrix. Combining that work with the supported-runtime cutover makes a failure harder to attribute and rollback.
Finish the ordinary 3.14 migration first. Establish its production baseline, then evaluate a free-threaded build against CPU-bound workloads and every native dependency as a separate release. Our free-threaded Python production guide covers the extension audit, concurrency testing, and measurements needed for that decision.
This sequencing does not reject the newer execution model. It preserves a clean answer to the immediate operational question: can the existing service run correctly on a supported interpreter?
Build a complete runtime inventory
Search the repository first:
rg -n \
'python:?3\.10|python-version|runtime.*python|PYTHON_VERSION|3\.10' \
Dockerfile* .github .gitlab-ci.yml pyproject.toml tox.ini \
.python-version compose*.yml infra scripts 2>/dev/null
Then inventory systems that may not live in the main repository:
- container base images and multi-stage build images;
- CI test and release matrices;
- infrastructure-as-code runtime fields;
- serverless functions and edge workers;
- scheduled commands, queue consumers, and batch images;
- data pipelines and orchestration workers;
- notebooks and managed notebook kernels;
- developer version-manager files;
- pre-commit hooks and documentation builds;
- monitoring or administration scripts installed directly on hosts;
- third-party platforms that build from a requirements file;
- compiled applications that embed CPython;
- native extensions built against a specific Python ABI.
For each entry, record owner, environment, current patch release, target version, build source, deployment artifact, last execution time, and validation method. Delete confirmed dead jobs instead of migrating them.
Inspect the running process as well as its manifest
A deployment file can say 3.14 while an old image is still serving traffic. Add a safe diagnostic field or startup log containing:
import platform
import sys
runtime = {
"python": platform.python_version(),
"implementation": platform.python_implementation(),
"executable": sys.executable,
}
Do not expose full environment variables or filesystem paths on a public endpoint. The goal is to prove which interpreter an artifact actually runs.
Capture the Python 3.10 baseline
Before changing the runtime, record how the current release behaves under representative traffic. Useful baselines include:
- request rate, error rate, and p50/p95/p99 latency;
- process startup and readiness time;
- CPU time and resident memory per instance;
- worker throughput and queue age;
- scheduled-job duration and result counts;
- database connection use and query latency;
- garbage-collection frequency if the service tracks it;
- dependency lockfile and installed-package report;
- image size and cold-start behavior;
- a sample of important serialized outputs.
Save an immutable reference image for the rollback window. Label it with source revision, Python patch version, dependency lock digest, and build date. Do not rebuild the rollback artifact later from floating package indexes.
The baseline prevents two common mistakes: declaring success because the new runtime starts, and blaming Python for a pre-existing performance problem.
Make the migration reproducible locally
Create a clean environment with the target interpreter. The exact version manager can vary, but the checks should not depend on packages already installed in a developer's global environment.
python3.14 -m venv .venv314
. .venv314/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip check
python -m pytest
For a pyproject.toml application, use the project's existing lock and build tool rather than maintaining a second ad hoc dependency path. Recreate the lock for the target Python when the lock format contains environment-specific resolution.
Print the interpreter at the start of test and build jobs:
python -VV
python -c 'import sys; print(sys.executable); print(sys.version_info)'
This catches a shell that activated the wrong environment or a CI step that calls a different python binary than the installer.
Audit dependency compatibility
Read Requires-Python, then prove installation
Python package metadata uses Requires-Python to declare compatible versions. The Python Packaging User Guide documents the field, but metadata quality varies. A declared range is evidence, not a complete test.
Resolve from a clean target environment. Look for packages held at older versions, conditional dependencies that change on Python 3.14, and transitive pins with no valid solution.
If production cannot compile native extensions, test wheel availability explicitly:
python3.14 -m pip install \
--only-binary=:all: \
--dry-run \
-r requirements.txt
A missing wheel may force an unexpected source build with a C compiler, Rust toolchain, system headers, and longer deployment time. Decide whether to upgrade the package, build a controlled internal wheel, replace the dependency, or remain on 3.13 temporarily.
Separate required upgrades from optional upgrades
Change the interpreter and the minimum dependency set needed to support it. Avoid combining the migration with a major web framework upgrade, database driver replacement, task-queue redesign, and new formatter configuration.
Small change sets create clearer failures. If the canary's memory grows, the team can compare the runtime and required dependency deltas instead of investigating a quarter's worth of unrelated modernization.
Check application packaging metadata
Libraries should advertise the versions they actually test:
[project]
requires-python = ">=3.11"
[project.classifiers]
# Include the Python versions exercised in CI.
Do not raise requires-python to 3.14 merely because the deployment now uses 3.14 if the distributed library still supports and tests 3.11 through 3.14. Application repositories can choose a single deployment runtime while reusable libraries publish a broader tested range.
Read every intermediate porting guide
A direct runtime jump still accumulates changes from 3.11, 3.12, 3.13, and 3.14. Read the “Porting to” and “Removed” sections for each release. Search the codebase for affected APIs before waiting for a test to stumble over them.
Python 3.13 removed old standard-library modules
PEP 594 removed a group of long-deprecated modules in Python 3.13, including cgi, cgitb, imghdr, mailcap, nntplib, pipes, telnetlib, uu, and xdrlib among others.
Search imports across application code, scripts, tests, and vendored utilities:
rg -n '^\s*(from|import)\s+(aifc|audioop|cgi|cgitb|chunk|crypt|imghdr|mailcap|msilib|nis|nntplib|ossaudiodev|pipes|sndhdr|spwd|sunau|telnetlib|uu|xdrlib)\b' .
Choose maintained replacements based on actual use. Installing a package with the same import name can preserve behavior temporarily, but it also creates a new dependency and security owner.
Python 3.14 changed multiprocessing defaults on many Unix systems
The official Python 3.14 porting notes state that forkserver becomes the default multiprocessing start method on Unix platforms other than macOS. Windows and macOS continue to use spawn.
Code that relied on inherited global state, unpicklable callables, open connections, or side effects from fork may fail. Test process pools and background workers through their real entry points. Put executable startup behind an if __name__ == "__main__": guard where required and initialize connections inside the child process rather than inheriting them accidentally.
Treat deprecation warnings as migration input
Run the suite with warnings visible:
python -W default -m pytest
Classify warnings by owner and dependency. Promote warnings from application packages to errors after the first cleanup, while allowing a temporary, documented filter for upstream dependencies. A blanket -W error can turn an unrelated third-party warning into noise that blocks useful migration work.
Test native extensions and binary boundaries
Packages backed by C, C++, Rust, Fortran, or Cython deserve a separate checklist. A wheel built for CPython 3.10 may not load on 3.14 unless it uses the stable ABI and carries a compatible tag.
For each native dependency:
- confirm a wheel exists for the target Python, OS, architecture, and libc;
- inspect whether production downloads a wheel or builds from source;
- run import tests in the final container image;
- execute the code path, not just
import package; - test CPU-specific wheels on the oldest supported hardware;
- verify external shared-library versions and search paths;
- rebuild internal extensions with the target headers and toolchain;
- run sanitizers or native test suites where the project supports them.
PyO3 projects must use a version that supports the target interpreter. Our Rust and PyO3 extension guide explains the boundary design and packaging choices that make those upgrades easier.
Expand the CI matrix before removing 3.10
During migration, CI should test the old and target runtimes against the same commit. That reveals whether a fix for 3.14 accidentally breaks the rollback image.
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.14"]
steps:
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- run: python -VV
- run: python -m pip install -r requirements.txt
- run: python -m pip check
- run: python -m pytest
Pin action majors according to the repository's dependency policy and review their release notes before changing them. Add 3.13 when it is a supported library version or a temporary production target. For an application that will run only on 3.14, keep the matrix narrow enough that failures remain actionable.
The matrix needs integration services too. Test the target interpreter against the supported database, cache, broker, object store, and authentication provider versions alongside isolated unit tests.
Rebuild the container from a supported base
Use a specific supported patch or a controlled digest rather than a floating tag during validation:
FROM python:3.14.1-slim@sha256:REPLACE_WITH_VERIFIED_DIGEST
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN python -m pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-m", "service"]
The patch and digest above are placeholders for the versions approved by your build process. Verify them at implementation time. Rebuild all stages: a 3.14 runtime paired with wheels built in a 3.10 builder can produce confusing ABI failures.
Inspect the resulting image:
docker run --rm app:migrate python -VV
docker run --rm app:migrate python -m pip check
docker run --rm app:migrate python -c 'import critical_extension'
Run the image as the same non-root user, with the same filesystem permissions, locale, time zone, certificates, and architecture used in production.
Validate behavior beyond unit tests
Exercise startup, shutdown, and background work
Runtime migrations often expose lifecycle assumptions. Test readiness, graceful shutdown, worker cancellation, retry behavior, scheduled tasks, and process pools. Measure whether the application can drain work before the deployment deadline.
Compare external contracts
Replay representative requests and compare status codes, response schemas, headers, serialization, sorting, numeric formatting, and error bodies. For event-driven services, compare message keys, types, ordering promises, and idempotency behavior.
Golden files need thoughtful review. Updating every expected output automatically can hide a real contract change.
Load test representative paths
Python 3.11 introduced substantial interpreter performance work, but the official 3.11 documentation warns that results depend on workload. I/O-heavy applications and work delegated to native libraries may see little change.
Measure your service. Compare CPU, memory, latency, throughput, startup, and garbage collection under the same input and concurrency. Avoid changing autoscaling thresholds until the new baseline is stable.
Our guide to Python hosting options can help when the current platform itself blocks supported runtimes. The runtime and hosting migration can share discovery, but they should retain separate rollback decisions.
Design the rollout and rollback together
Deploy the same application revision on both runtimes
The cleanest canary changes Python and the minimum required dependencies while keeping business features constant. Send a small share of production traffic or jobs to the 3.14 image.
Compare:
- exceptions grouped by type and code location;
- latency and timeout rate;
- CPU and resident memory;
- worker throughput and retry count;
- database and cache connection behavior;
- scheduled-job result totals;
- startup, readiness, and shutdown time;
- output contract mismatches.
Increase traffic only after a full representative workload window. A service with weekly billing or month-end processing needs a longer validation plan than a stateless API whose main paths run every minute.
Keep data changes backward compatible
The previous 3.10 image can roll back safely only if both versions understand the current database schema, message format, and cache keys. Use expand-and-contract migrations:
- Add fields or tables in a backward-compatible form.
- Deploy code that can read old and new forms.
- Backfill separately.
- Remove old forms after the runtime rollback window closes.
Do not pair the interpreter upgrade with a destructive database migration. An old image that cannot read the new schema is not a rollback.
Define rollback triggers before the canary
Examples include a sustained error-rate increase, a p99 latency regression beyond an agreed threshold, memory growth that reduces safe instance density, failed scheduled jobs, output mismatches, or a native crash.
Specify who can roll back, which artifact receives traffic, and how queued work is handled. A rollback should be a tested operation, not a meeting held during an incident.
Update operational policy after cutover
Once the 3.14 release completes its rollback window:
- remove 3.10 from CI unless a distributed library still supports it;
- reject new images or functions configured with Python 3.10;
- update
requires-python, runtime files, and developer documentation; - delete obsolete 3.10 environments and cached wheels;
- rotate base-image monitoring to the chosen 3.14 line;
- update vulnerability scanners and asset inventory;
- close temporary warning filters with owners;
- schedule the next Python support review before 3.14 enters security-only maintenance.
Policy prevents regression. Without it, an old template or copied workflow can reintroduce 3.10 months after the main service migrated.
Common migration mistakes
Upgrading only the web process
Queue workers, scheduled jobs, migrations, and admin scripts often use separate images or runtime settings. Inventory by execution surface, not repository.
Reusing the old virtual environment
Virtual environments contain interpreter-specific paths and native artifacts. Create a new environment with the target Python and reinstall from the declared dependency source.
Assuming a successful install proves compatibility
The resolver proves it found a set of distributions. It does not exercise imports, native calls, multiprocessing, serialization, or framework startup.
Taking the largest dependency upgrade at the same time
Required dependency changes belong in the runtime migration. Optional framework redesigns belong in later releases with their own tests and rollback.
Relying on a floating container tag
A mutable tag can change the interpreter or operating system underneath a supposedly repeatable canary. Validate a patch release and digest, then use an intentional update process.
Claiming a speedup before measuring the service
Newer CPython versions improve many pure-Python workloads. Your application may be constrained by I/O, a database, a native library, or an external API. Keep performance claims attached to measured workload evidence.
Keeping 3.10 as an indefinite fallback
Rollback is a short incident-control mechanism. Once confidence is established, remove the unsupported image and the ability for new deployments to select it.
A production migration checklist
- Python 3.10.22 is recognized as the final 3.10 release, not a new support window.
- Python 3.14 is the target, or a documented blocker justifies 3.13 temporarily.
- Every runtime surface has an owner and validation method.
- The current production baseline and immutable rollback image are stored.
- Dependencies resolve from a clean target environment.
- Required wheels exist for every production OS and architecture.
- Removed standard-library modules and intermediate porting notes are audited.
- Native extensions are rebuilt and their real code paths are exercised.
- CI tests old and target runtimes during the rollback-compatible phase.
- Containers rebuild every interpreter-dependent stage.
- Unit, integration, contract, lifecycle, and load tests pass.
- The canary compares errors, latency, CPU, memory, throughput, and outputs.
- Database and message changes remain backward compatible.
- Rollback triggers, owner, artifact, and procedure are written down.
- Python 3.10 is blocked from new deployments after cutover.
- The next runtime review has a date and owner.
Python 3.10's end of life is an operational deadline, not a reason for a rushed one-line version change. The durable migration maps every interpreter, proves the dependency graph on clean environments, tests cumulative language changes, rebuilds native artifacts, and compares production behavior through a controlled canary.
Target Python 3.14 when the ecosystem permits it. Use 3.13 only for a named blocker with an exit plan. Once the new runtime has survived its representative workload window, close the rollback path and enforce the supported version in CI and deployment policy.
FAQs
When did Python 3.10 reach end of life?
Python 3.10 reached end of life on October 1, 2026. Python 3.10.22 was the final security release, and the 3.10 codebase is now frozen with no further updates or bug reports accepted.
Which Python version should replace Python 3.10?
Python 3.14 is the default target for most production applications because it is in bugfix support and is scheduled through October 2030. Python 3.13 can be a temporary target when a critical dependency or platform does not yet support 3.14. Avoid moving only to 3.11 because its support ends in October 2027.
Can I upgrade directly from Python 3.10 to 3.14?
Yes, if the application and every dependency support 3.14. Test the intermediate porting notes for 3.11, 3.12, 3.13, and 3.14 because removals and behavior changes accumulate even when the deployment jumps directly to the final version.
What usually breaks in a Python 3.10 migration?
Common failures include incompatible dependency pins, missing binary wheels, removed standard-library modules, changed multiprocessing behavior, C-extension build errors, warnings promoted to errors, and base images or serverless runtimes that still select Python 3.10.
How do I check whether dependencies support a newer Python?
Resolve and install from a clean environment using the target interpreter, inspect each package's Requires-Python metadata, require binary wheels where production cannot compile extensions, run pip check, and execute the full test suite. A successful resolver run alone does not prove runtime compatibility.
Should a migration also update every dependency?
Update only what the target runtime requires, then separate optional framework and feature upgrades into later changes. A focused runtime migration is easier to review, measure, and roll back than a single release that changes Python, frameworks, databases, and application behavior together.
How long should the Python 3.10 rollback remain available?
Keep the previous immutable application image only for a short, documented rollback window while the new runtime is canaried. Do not treat rollback as extended support, and ensure database or message-schema changes remain backward compatible for both images during that window.
Work with us
Let's build something together
We build fast, modern websites and applications using Next.js, React, WordPress, Rust, and more. If you have a project in mind or just want to talk through an idea, we'd love to hear from you.
Related Articles
Next.js • 24 min
Next.js 16.3.8 Security Update: Upgrade and Verify
Patch Next.js to 16.3.8, map seven security issues to exposed features, rebuild clean artifacts, invalidate risky caches, inspect evidence, and verify rollout.
10/3/2026
Engineering • 20 min
Cloudflare Worker Previews for Safer Coding Agents
A practical guide to Cloudflare Worker Previews for coding agents, including CI setup, data isolation, access controls, testing, observability, and cleanup.
9/23/2026
Engineering • 21 min
GitHub SSH Changes for 2026: RSA, SHA-1, and ML-KEM
Prepare Git clients, CI runners, IDEs, and deployment systems for GitHub's 2026 SSH changes to RSA/SHA-1, DH key exchange, RSA key sizes, and ML-KEM.
9/23/2026