The best Docker base image for your Python application (May 2024)

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.12

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.

Python on Docker Production Handbook 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 Stable, Ubuntu LTS, and RedHat Enterprise Linux and clones. The latest versions:

Distribution Released End-of-life Image Python versions
Ubuntu 24.04 2024 2029 ubuntu:24.04 3.12
Debian “Bookworm” 12 2023 2028 debian:12 3.11
RHEL 9 2022 2027 RedHat registry 3.12, 3.11, 3.9

Older versions, which you probably don’t want to start with for a new project:

Distribution Released End-of-life Image Python versions
Ubuntu 22.04 2022 2027 ubuntu:22.04 3.10
Debian “Bullseye” 11 2021 2025 debian:11 3.9
RHEL 8 2019 2024 RedHat registry 3.9, 3.8

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 RHEL clones: OracleLinux, RockyLinux or Alma Linux.
  • You can get long term security updates for older distributions, but that’s a bad idea; better to upgrade more often.

Option #2: The “official” Python Docker image

Another alternative is Docker’s own “official” python image, which comes pre-installed with respective versions of Python (3.10, 3.11, 3.12, etc.), and has multiple variants:

  • Alpine Linux, which as I explained above I don’t recommend using.
  • Debian “Bookworm” 12, 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 “Bookworm” 12, with a 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.11-slim-bookworm is 51MB, and python:3.11-alpine3.18 is 19MB. Their uncompressed on-disk sizes are 149MB and 52MB respectively.

There are also versions based on older versions of Debian, “Bullseye” 11 and “Buster” 10.

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.

Focusing on point releases, at one point (September 2022), 3.9.14 had just been released less than two weeks previously; v3.9.12 was released in March 2022, six months before that. Here is what the different images provided at the time:

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 do 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

Depending how Python is compiled you can get different speeds, even for the same version. I’ve written a separate article where I ran some benchmarks comparing multiple Python builds. For Python 3.9, the choice of base image made a difference. 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.12, Ubuntu 24.04 is about 10% faster than python:3.12, so this can make a difference at scale.

System packages

  • Ubuntu 24.04 is the most up-to-date in terms of system packages and libraries, since it was released in April 2024.
  • The “official” Docker python images are based off Debian 12 if you use the default or bookworm variants, so they are only a year behind.
  • RHEL 9 is even older, but gets additional packages added on, for example Python 3.12 was added in RHEL 9.4.

Ease of use and annoyances

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 24.04.

Lacking specific constraints, I’d probably choose the official Docker Python image (python:3.12-slim-bookworm). This will ensure you have access the latest Python bugfixes.