Skip to content

How do I set up a Python 2 development environment with Docker in 2026?

I stared at my terminal in disbelief. Python 2.7 was supposed to be dead—like, really dead. End-of-life since January 1, 2020. Yet here I was, six years later, trying to maintain a legacy codebase that absolutely refused to die.

$ python --version
Python 3.11.5
$ python2 --version
command not found: python2

Great. My modern macOS system doesn’t even recognize Python 2 anymore. And when I tried to install it via Homebrew?

$ brew install python@2
Error: python@2 has been disabled because it is past its end-of-life date!

Of course it has. But I still needed to run and test this legacy application. Time to figure out how to safely work with Python 2 in 2026 without contaminating my entire system.

The Problem with Python 2 in 2026

Here’s what I learned the hard way: modern operating systems have completely purged Python 2. Package managers don’t include it. Security vulnerabilities are unpatched. Even pip versions have dropped Python 2 support.

But many developers still need Python 2 for:

  • Legacy codebases awaiting migration
  • Jython projects (Python running on Java)
  • Libraries that never made the Python 3 transition
  • Testing compatibility before migration

I tried a few approaches before finding the right one.

Approach 1: pyenv (Failed Miserably)

First, I tried installing Python 2 via pyenv on my Mac:

Terminal window
$ pyenv install 2.7.18
python-build: use [email protected] from homebrew
python-build: use readline from homebrew
Downloading Python-2.7.18.tar.xz...
-> https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tar.xz
Installing Python-2.7.18...
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

After hours of trying to compile OpenSSL 1.1 (also deprecated) and fighting with Xcode command line tools, I gave up. The system dependencies for Python 2 are just too old.

Approach 2: Docker (The Solution!)

Then I remembered: Docker images are frozen in time. I could get a container with Python 2.7 pre-installed and working, without touching my host system.

$ docker pull python:2
2: Pulling from library/python
...
Status: Downloaded newer image for python:2
docker.io/library/python:2
$ docker run -it python:2 python --version
Python 2.7.18

That’s it. Python 2.7.18 running instantly, completely isolated from my host system. No compilation errors, no dependency hell.

Building a Development Environment

A one-off container is fine for quick tests, but I needed a proper development setup. Here’s the Dockerfile I created:

FROM python:2.7.18-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN pip install --no-cache-dir \
pytest \
mock \
nose \
coverage \
pylint \
flake8 \
"pycodestyle<2.8" \
pydocstyle \
tox \
setuptools \
wheel \
twine \
virtualenv \
"autopep8<1.6" \
yapf
COPY . .
CMD ["python", "app.py"]

Notice the version constraints? That bit me hard. I initially tried:

Terminal window
$ pip install pycodestyle autopep8
# Later, inside Python 2...
ImportError: cannot import name 'SpecifierSet'

Turns out, modern versions of these tools dropped Python 2 support. I had to pin to older versions:

  • pycodestyle<2.8 (latest requires Python 3)
  • autopep8<1.6 (same story)

The Version Constraint Gotcha

Here’s what happened when I didn’t pin versions:

Collecting pycodestyle
Downloading pycodestyle-2.12.1-py2.py3-none-any.whl (42 kB)
...
Successfully installed pycodestyle-2.12.1
$ pycodestyle --version
Traceback (most recent call last):
File "/usr/local/bin/pycodestyle", line 7, in <module>
from pycodestyle import _main
File "/usr/local/lib/python2.7/site-packages/pycodestyle.py", line 83, in <module>
from typing import Optional, Set
ImportError: No module named typing

Python 2 doesn’t have the typing module. The tool installed, but it won’t run. Always check Python 2 compatibility.

Docker Compose for Easier Development

Typing docker build and docker run constantly got tedious. I created a docker-compose.yml:

version: '3.8'
services:
app:
build: .
volumes:
- ./src:/app
- ./data:/app/data
environment:
- PYTHONPATH=/app
command: python main.py
tools:
image: python:3.11-slim
volumes:
- ./src:/app
working_dir: /app
command: bash

Now I can run my Python 2 app with docker-compose up app, and still have a Python 3 container for modern tooling like black (which doesn’t support Python 2).

CentOS 6 Alternative (Better Tooling)

I discovered another approach on Reddit: using CentOS 6, which has Python 2 as its system Python.

┌─────────────────────────────────────────────────┐
│ Container Architecture │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ CentOS 6 │ │ python:2.7.18-slim │ │
│ ├─────────────┤ ├─────────────────────┤ │
│ │ Python 2.6 │ │ Python 2.7.18 │ │
│ │ (system) │ │ (Debian-based) │ │
│ │ │ │ │ │
│ │ + yum repos │ │ + pip install │ │
│ │ + older pkgs│ │ + smaller image │ │
│ └─────────────┘ └─────────────────────┘ │
│ │
│ Legacy tooling Modern Python 2 setup │
│ works better Lightweight & common │
│ │
└─────────────────────────────────────────────────┘

The Dockerfile:

FROM centos:6
RUN yum update -y && \
yum install -y python-pip python-devel && \
yum clean all
WORKDIR /app
RUN pip install pytest mock nose coverage pylint
COPY . .
CMD ["python", "app.py"]

Why CentOS 6? The package repositories still contain Python 2-compatible versions of common tools. Less version pinning needed.

Hybrid Approach: Two Virtual Environments

For my project, I ended up using two virtual environments inside the container:

FROM python:2.7.18-slim
WORKDIR /app
RUN pip install virtualenv
# Application dependencies (Python 2)
RUN virtualenv .venv/app
# Modern tools that need Python 3 (black, mypy, etc.)
# Run these from host or separate container

This keeps my application dependencies isolated from development tools:

/app
├── .venv/
│ ├── app/ # Python 2.7 for legacy app
│ └── app_tools/ # Could use Python 3 for modern tooling
├── src/
│ └── main.py
└── requirements.txt

Common Mistakes I Made

Mistake 1: Forgetting to rebuild the image

$ docker run my-python2-app
ImportError: No module named 'new_dependency'

I added a dependency to requirements.txt but forgot to rebuild. Docker doesn’t automatically reinstall.

Mistake 2: Mixing Python 2 and 3 in the same virtualenv

$ virtualenv .venv
$ .venv/bin/pip install black
ERROR: black requires Python 3.6+

Don’t try to force Python 3 tools into a Python 2 environment. Use separate containers or virtualenvs.

Mistake 3: Running Python 2 in production

I was tempted to deploy my Docker container to production. Bad idea. Python 2 hasn’t received security patches since 2020. Development and testing only.

Security Considerations

Let me be clear: Python 2 has known vulnerabilities. The OpenSSL version in the official image is old. No security updates will ever come.

I treat Python 2 containers as disposable:

  • Never expose them to the internet
  • Never use them in production
  • Keep them isolated from my host filesystem
  • Delete and recreate containers frequently
┌──────────────────────────────────────────────┐
│ Security Boundary │
│ │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ Host System │ │ Docker Network │ │
│ │ (Python 3) │ │ (Isolated) │ │
│ │ │ │ │ │
│ │ ✓ Secure │ │ ┌───────────┐ │ │
│ │ ✓ Patched │ │ │ Python 2 │ │ │
│ │ ✓ Modern │ │ │ Container │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ ⚠ Legacy │ │ │
│ │ │ │ │ ⚠ Unpatched│ │ │
│ └────────────────┘ │ └───────────┘ │ │
│ ↑ │ │ │
│ │ └─────────────────┘ │
│ │ ↑ │
│ No direct access │ │
│ (use volumes only) Read-only access │
│ (if possible) │
└──────────────────────────────────────────────┘

Quick Reference: Essential Commands

Here’s my cheat sheet for working with Python 2 Docker containers:

Terminal window
# Pull and verify
docker pull python:2
docker run -it python:2 python --version
# Interactive development
docker run -it -v $(pwd):/app -w /app python:2 bash
# Build and run with Dockerfile
docker build -t py2-project .
docker run -it py2-project
# Using docker-compose
docker-compose up app
docker-compose exec app bash
# Clean up (do this often)
docker container prune
docker image prune

Why This Matters

Containerizing Python 2 development isn’t just about convenience—it’s about containment (pun intended). By isolating legacy code:

  • My host Python 3 environment stays clean
  • I can run multiple Python 2 projects with different dependencies
  • CI/CD pipelines can test legacy code reproducibly
  • Security risks are contained within the Docker sandbox
  • Migration becomes a planned project, not an emergency

The alternative—installing Python 2 on my host—would create conflicts, break tools, and leave security holes. Docker gives me a fresh, reproducible environment every time.

What’s Next?

If you’re still maintaining Python 2 code in 2026, you’re not alone. But every hour spent in a Python 2 container should be an hour planning your migration to Python 3. The container is a safety net, not a permanent home.

Start with the basic Dockerfile I shared, add your project’s specific dependencies (with version constraints!), and get that legacy code running in isolation. Then, start planning your Python 3 migration—you’ll thank yourself later.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments