DEV Community

Mario García
Mario García

Posted on • Updated on

Python: Docker Image Optimization

I've created a Python app that connects to the Forem's API to get a list of the articles I wrote on DEV, and it displays the result on a web page built with Flask. The source code is available on a GitLab repository.

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"]


Enter fullscreen mode Exit fullscreen mode

Now build the image by running:



$ docker build . -t blog


Enter fullscreen mode Exit fullscreen mode

If you run the following command:



$ docker image ls blog


Enter fullscreen mode Exit fullscreen mode

You'll get the following output:



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


Enter fullscreen mode Exit fullscreen mode

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"]


Enter fullscreen mode Exit fullscreen mode

Build the image:



$ docker build . -t blog


Enter fullscreen mode Exit fullscreen mode

Run the following command:



$ docker image ls blog


Enter fullscreen mode Exit fullscreen mode

This is the output you get:



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


Enter fullscreen mode Exit fullscreen mode

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 to optimize a containerized Rust app, and as it is explained here, this solution could also work for Python apps.

Create a Dockerfile as follows, with multi-stage builds and venv:



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"]


Enter fullscreen mode Exit fullscreen mode

Build the image:



$ docker build . -t blog


Enter fullscreen mode Exit fullscreen mode

Run the following command:



$ docker image ls blog


Enter fullscreen mode Exit fullscreen mode

This is the output you get:



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


Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)