The best Docker base image for your Python application (March 2023)
When you’re building a Docker image for your Python application, you’re building on top of an existing image—and there are many possible choices for the resulting container.
There are OS images like Ubuntu, and there are the many different variants of the python
base image.
Which one should you use? Which one is better? There are many choices, and it may not be obvious which is the best for your situation.
So to help you make a choice that fits your needs, in this article I’ll go through some of the relevant criteria, and suggest some reasonable defaults that will work for most people.
What is a base image?
Most Docker images aren’t built from scratch.
Instead, you take an existing image and use it as the basis for your image using the FROM
command in your Dockerfile:
FROM python:3.11
Docker has a series of “official” Docker base images based on various Linux distributions, and also base images that package specific programming languages, in particular Python.
Note: Outside any specific best practice being demonstrated, the Dockerfiles in this article are not examples of best practices, since the added complexity would obscure the main point of the article.
Need to ship quickly, and don’t have time to figure out every detail on your own? Read the concise, action-oriented Python on Docker Production Handbook.
What do you want from a base image?
There are a number of common criteria for choosing a base image, though your particular situation might emphasize, add, or remove some of these:
- Stability: You want a build today to give you the same basic set of libraries, directory structure, and infrastructure as a build tomorrow, otherwise your application will randomly break.
- Security updates: You want the base image to be well-maintained, so that you get security updates for the base operating system in a timely manner.
- Up-to-date dependencies: Unless you’re building a very simple application, you will likely depend on operating system-installed libraries and applications (e.g. a compiler). You’d like them not to be too old.
- Extensive dependencies: For some applications less popular dependencies may be required—a base image with access to a large number of libraries makes this easier.
- Up-to-date Python: While this can be worked around by installing Python yourself, having an up-to-date Python available saves you some effort.
- Small images: All things being equal, it’s better to have a smaller Docker image than a bigger Docker image.
The need for stability suggests not using operating systems with limited support lifetime, like Fedora or non-LTS Ubuntu releases.
Why you shouldn’t use Alpine Linux
A common suggestion for people who want small images is to use Alpine Linux, but that can lead to longer build times, obscure bugs, and performance issues.
You can see the linked article for details, but I recommend against using Alpine.
Option #1: Ubuntu LTS, RedHat Universal Base Image, Debian
There are three major operating systems that roughly meet the above criteria: Debian “Bullseye” 11, Ubuntu 20.04 LTS, and RedHat Enterprise Linux 8. Ubuntu and RHEL have backported newer versions of Python, so even though they are older than Debian 11 they still include Python 3.9.
Distribution | Released | End-of-life | Image | Python versions |
---|---|---|---|---|
RHEL 8 | May 2019 | May 2024 | RedHat registry | 3.9, 3.8, 3.6 |
Debian 11 | Aug 2021 | Aug 2025 | debian:11 |
3.9 |
Ubuntu 20.04 | Apr 2020 | Apr 2025 | ubuntu:20.04 |
3.9, 3.8 |
Ubuntu 22.04 | Apr 2022 | Apr 2027 | ubuntu:22.04 |
3.10 |
RHEL 9 + clones | May 2022 | May 2027 | RedHat registry | 3.9 (3.11 likely in future) |
Additional notes:
- Previous versions of this article covered CentOS, but CentOS is no longer a long-term stable operating system. You can instead use other clones: OracleLinux, RockyLinux or Alma Linux.
- RHEL 8 will have security updates until 2029, RHEL 9 until 2032. Ubuntu 22.04 will have security updates until 2032, and 20.04 until 2030.
- You can get longer security updates for older distributions, but that’s a bad idea; better to upgrade more often.
Option #2: The Python Docker image
Another alternative is Docker’s own “official” python
image, which comes pre-installed with respective versions of Python (3.9
, 3.10
, 3.11
, etc.), and has multiple variants:
- Alpine Linux, which as I explained above I don’t recommend using.
- Debian “Bullseye” 11, with many common packages installed. The image itself is large, but the theory is that these packages are installed via common image layers that other official Docker images will use, so overall disk usage will be low.
- Debian 11
slim
variant. This lacks the common packages’ layers, and so the image itself is much smaller, but if you use many other “official” Docker images the overall disk usage will be somewhat higher.
For comparison purposes, the download size of python:3.10-slim-bullseye
is 45MB, and python:3.10-alpine
is 18MB.
Their uncompressed on-disk sizes are 125MB and 48MB respectively.
Comparing the options
Python versions
The “official” Docker image has the benefit of providing every version of Python you might want, and tends to be very up-to-date with bugfix releases. This is not necessarily the case with the long-term support distributions. The table above shows the versions they include (omitting Python 2, which you really shouldn’t be using anymore).
Focusing on point releases, at the time of writing (September 2022), 3.9.14 has just been released less than two weeks ago; v3.9.12 was released in March 2022, six months ago. Here is what the different images provide:
Image | 3.9 release |
---|---|
Debian 11 | 3.9.2 |
Ubuntu 20.04 | 3.9.5 |
RHEL 8 | 3.9.7 |
RHEL 9 | 3.9.10 |
Docker python |
3.9.14 |
As you can see, Ubuntu and RedHat are doing a better but still rather lackluster job at staying up-to-date, but the “official” Docker image is the only one that actually includes the latest point release. You can however assume that all of them will include major security updates, at least.
Performance
I’ve written a separate article where I ran some benchmarks comparing multiple Python builds, but here is a quick update using the pystone
benchmark: not a perfect benchmark, but correlated with overall performance enough that I think it’s meaningful.
The performance measure is kilopystone/sec; higher is better, numbers are approximate.
For Python 3.9:
Image | Performance |
---|---|
Debian 11 | ~255 |
Ubuntu 20.04 | ~255 |
RHEL 8 | ~245 |
python:3.9-bullseye |
~222 |
Debian and Ubuntu are the fastest, RHEL 8 is slightly slower, and the Docker Python images are even slower.
However, Python 3.10 includes some performance optimizations in the build by default, and shows no real difference between python:3.10-bullseye
image ubuntu:22.04
.
For Python 3.10, then, it doesn’t seem to matter which option you choose, though there’s no RedHat-flavored 3.10 to compare, at the moment.
System packages
- Ubuntu 22.04 and RHEL 9 and clones are the most up-to-date in terms of system packages and libraries.
- The “official” Docker
python
image is based off Debian 11, which is about a year behind those two. - RHEL 8 does backport newer versions of applications, however, for example Python 3.9 support.
Ease of use and annoyances
- Both the
python:3.11-slim-bullseye
andregistry.access.redhat.com/ubi9/python-39
images ship preconfigured with Python. - With Debian and Ubuntu images, you need to add a couple of lines to your
Dockerfile
to install Python—a minor inconvenience. - Ubuntu 20.04 will end up installing Python 3.8 as a side-effect of installing Python 3.9, which is annoying and leads to larger images. However, given the existence of Python 3.10 and 3.11, and Ubuntu 22.04, it’s probably worth moving on from 20.04.
So which should you use?
- If you’re a RedHat shop, you’ll want to use their image or one of their clones.
- If you want the absolute latest bugfix version of Python, or a wide variety of versions, the official Docker Python image is your best bet.
- If you want the absolute latest system packages, you’ll want Ubuntu 22.04; RedHat 9 is somewhat more conservative, for example including Python 3.9 rather than 3.10.
- If you care about performance and you’re using an older version of Python (3.9 or earlier), Debian 11 or Ubuntu 20.04 will give you one of the fastest builds of Python; Ubuntu does better on point releases, but will have slightly larger images (see above). The difference is at most 10% though, and many applications are not bottlenecked on Python performance.
Lacking specific constraints, I’d probably choose the official Docker Python image (python:3.11-slim-bullseye
).
This will ensure you have access the latest Python bugfixes, and unlike previous versions of Python it seems you won’t be paying a performance cost.