Python: Docker Image Optimization
Tags: tutorial, python, docker
Published: 2024-01-12 03:50:52

I've created a Python app that connects to the Forem's API to get a list of the articles I wrote on [DEV](https://dev.to/), and it displays the result on a web page built with Flask. The source code is available on a GitLab [repository](https://gitlab.com/mattdark/python-blog/).

To containerize this app with Docker, create a `Dockerfile` with the following content:

```
FROM python:latest

WORKDIR /app

COPY requirements.txt .
RUN pip install --upgrade pip --no-cache-dir
RUN pip install -r requirements.txt --no-cache-dir

COPY . .

EXPOSE 8080

ENTRYPOINT ["gunicorn","--config", "gunicorn_config.py", "app:app"]
```

Now build the image by running:

```
$ docker build . -t blog
```

If you run the following command:

```
$ docker image ls blog
```

You'll get the following output:

```
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
blog         latest    3debcac78e45   20 minutes ago   1.05GB
```

The image size is `1.05GB`. How to optimize the Docker image?

There are a few approaches that can be analyzed:

* Use smaller base images
* Multi-stage builds

Through this blog post, you'll learn how to optimize your Docker image through both solutions.

## Use Smaller Base Images
Using a smaller base image can help reduce the image size. Replace the `python:latest` image with the `python:alpine3.19` image:

```
FROM python:alpine3.19

WORKDIR /app

COPY requirements.txt .
RUN pip install --upgrade pip --no-cache-dir
RUN pip install -r requirements.txt --no-cache-dir

COPY . .

EXPOSE 8080

ENTRYPOINT ["gunicorn","--config", "gunicorn_config.py", "app:app"]
```

Build the image:

```
$ docker build . -t blog
```

Run the following command:

```
$ docker image ls blog
```

This is the output you get:

```
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
blog         latest    2b3085ad2c5d   19 seconds ago   81.1MB
```

As you see, the image size was reduced from `1.05GB` to `81.1MB`.

## Multi-stage builds
I wrote about how to use [multi-stage builds](https://dev.to/mattdark/rust-docker-image-optimization-with-multi-stage-builds-4b6c) to optimize a containerized Rust app, and as it is explained [here](https://pythonspeed.com/articles/multi-stage-docker-python/), this solution could also work for Python apps.

Create a `Dockerfile` as follows, with multi-stage builds and [venv](https://docs.python.org/3/library/venv.html):

```
FROM python:alpine3.19 as builder

ENV PATH="/app/venv/bin:$PATH"

WORKDIR /app

RUN python -m venv /app/venv
COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

FROM python:alpine3.19

WORKDIR /app

ENV PATH="/app/venv/bin:$PATH"

COPY . .
COPY --from=builder /app/venv /app/venv

ENTRYPOINT ["gunicorn", "--config", "gunicorn_config.py", "app:app"]
```

Build the image:

```
$ docker build . -t blog
```

Run the following command:

```
$ docker image ls blog
```

This is the output you get:

```
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
blog         latest    2b3085ad2c5d   19 seconds ago   76.2MB
```

The image was reduced a little more, compared to the result obtained while only using a smaller base image.

## Conclusion
Through this blog post, you learned how to optimize your containerized Python app, through different solutions, and combining all the practices described here.

---

[![Support me on Buy Me A Coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzx9uenqvwqv1vp8ub5d.png)](https://buymeacoffee.com/letstalkoss)