Upgrade the Python Version of a Production Django App

Upgrade a production Django app to a newer Python version safely: check Django and dependency support, run tests on both versions in CI, rebuild virtualenvs or images, upgrade Celery workers together, deploy gradually, and roll back cleanly.

Problem statement

Each Python version receives security fixes for about five years, and newer versions bring real performance improvements. Running an end-of-life Python means no security patches and, eventually, dependencies that drop support. Upgrading a production Django app from, say, 3.10 to 3.13 touches every layer: Django's supported versions, compiled dependencies, the Docker base image or server packages, virtualenvs, Celery workers, and CI. Done as a deliberate, tested change, it is routine; done as a side effect of an OS upgrade, it breaks things.

A Python upgrade pathCheck supportDjango + depsCI matrixold and new PythonRebuildimage or venvDeploy everythingweb, workers, beatTest on both versions, switch every process together, and keep a rollback image.
  1. Check that your Django version supports the target Python (Django's FAQ lists supported versions) and upgrade Django first if needed.
  2. Update dependencies that lack wheels or support for the new version (pip install --dry-run or uv lock against it).
  3. Run CI on both the current and target Python versions until the switch.
  4. Fix deprecation warnings: run tests with -W error::DeprecationWarning for your own code.
  5. Rebuild the Docker image on the new base (python:3.13-slim) or create new virtualenvs; never upgrade in place.
  6. Deploy web, Celery workers, and Beat on the new version together; keep the previous image or venv for rollback.

Step-by-step solution

1. Check compatibility

Django supports specific Python versions per release (for example Django 5.2 supports 3.10-3.13; check the current table). If your Django is older than the target Python, upgrade Django first, separately. Then check dependencies:

uv python install 3.13
uv venv /tmp/py313 --python 3.13
uv pip install --python /tmp/py313/bin/python -r requirements.txt

Failures usually come from old pins of compiled packages (psycopg2, lxml, numpy, Pillow, cryptography) without wheels for the new version, or packages using removed standard-library modules (Python 3.12 removed distutils, 3.13 removed several legacy modules such as cgi and telnetlib).

2. Test on both versions

strategy:
  matrix:
    python-version: ["3.11", "3.13"]
steps:
  - uses: actions/setup-python@v5
    with: { python-version: "${{ matrix.python-version }}" }
  - run: pip install -r requirements.txt
  - run: python -W error::DeprecationWarning -m pytest

Running both catches version-specific failures while keeping the current version deployable.

a safe Python upgrade1Confirm Django supports targetupgrade Django first if not2Resolve deps on new Pythonwheels, removed modules3CI matrix: old and newfix warnings4Rebuild image or venvnever in place5Deploy all processes togetherrollback image readyTreat the interpreter like any other dependency upgradedjango-deployment.com
Upgrade one minor version at a time if you are several behind.

3. Rebuild, do not upgrade in place

  • Docker: change the base image tag (FROM python:3.13-slim) and rebuild; the old image remains your rollback.
  • Servers: install the new Python (distro package, deadsnakes, or uv python install), create a new virtualenv at a versioned path, install the lock file, and switch a symlink.

Virtualenvs are tied to the interpreter that created them; see Fix a Broken Django Virtualenv After an OS Upgrade.

4. Upgrade every process together

Web, Celery workers, Beat, and scheduled commands should run the same Python. Celery messages are serialised as JSON by default, so mixed versions usually interoperate, but pickled data (if you use pickle for results or cache values) may not be compatible across versions. Clear or version caches that store pickled objects if you see deserialisation errors.

5. Deploy gradually and watch

Roll out to staging, then a canary or one server, and compare error rates and latency with the old version. Python 3.11+ is often noticeably faster for Django code; measure it so you can see the gain.

what breaks and how to fix itSymptomCause and fixpip builds packages for minutesold pin without wheels;upgrade the packageNo module named 'distutils'removed in 3.12;upgrade the dependencyDjango refuses to startDjango version too old;upgrade Django firsterrors unpickling cache valuespickled data acrossversions; clear cachesvenv broken on the servertied to old interpreter; recreate itMost failures are old dependency pins, caught early by a CI matrixdjango-deployment.com
Fix problems in CI before they reach any server.

Verification check: python --version in every running process type (web, worker, beat) shows the new version, the test suite passes on it in CI, error rates after deploy match the previous version, and a rollback to the previous image has been rehearsed.

Explanation

Python minor versions change the bytecode, the C API that compiled extensions use, and the standard library (removing deprecated modules). Compiled packages therefore need builds for the new version, and pure-Python packages need to avoid removed APIs. Django declares the Python versions each release supports. Treating the interpreter as a dependency, tested in CI on both versions and deployed by rebuilding images or environments, turns the upgrade into a normal, reversible release rather than a risky in-place change.

Edge cases / notes

  • Free-threaded builds (3.13t) and the experimental JIT are opt-in; most deployments use the standard build.
  • Managed platforms set the Python version through a file (.python-version, runtime.txt) or base image; update it there.
  • Lambda and serverless runtimes have their own supported Python lists.
  • Type checkers and linters may need updates to understand new syntax.

Next steps

FAQ

How do I upgrade Python for a Django project?

Check Django and dependency support, test on both versions in CI, rebuild the image or virtualenv, and deploy all processes together.

Which Python version does my Django version support?

Django's documentation FAQ lists supported Python versions for each release.

Can I upgrade Python in place on the server?

Avoid it. Create a new virtualenv or image with the new Python and switch to it.

Is a newer Python faster for Django?

Usually yes; 3.11 and later brought significant speed-ups. Measure on your workload.

Keep going