containers

Containerize a Python application in 3 steps

Reusable dockerfiles to easily containerize Python applications (based on pip or poetry)

by Christian Barra
January 11, 2021

Table of Contents:

  1. Intro
  2. Containerize a Python application with requirements.txt
  3. Containerize a Python application with poetry
  4. Build the docker image
  5. Run the docker image
  6. Push the docker image
  7. What's next?

This article will show you how to containerize a Python application and run it in three single steps. This is a shorter version of How to write a great Dockerfile for Python apps, for a more in depth explanation I recommend you to check it.

I decided to containerize a Django application but the dockerfiles below work with any kind of Python application, you can use them as examples to containerize your Python applications.

Intro

The dockerfiles in this article have some nice features:

  • smart use of docker cache
  • running as non-root
  • you can pass a GIT_HASH
  • use tini

You can reuse the dockerfiles from this article with any Python project as long as you have:

  • a working Python project
  • a list of dependencies you want to install (either with pip or poetry)

Containerize a Python application with requirements.txt

If you are managing your dependencies directly with pip you can create your requirements.txt manually,

or with pip freeze > requirements.txt.

You can create a new Dockerfile inside the root path of your project and copy-paste the code below

# 💥 change the Python version with your version 💥
FROM python:3.8.3-slim-buster

ENV TINI_VERSION="v0.19.0"
ENV PIP_NO_CACHE_DIR=True

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini

RUN pip install -U \
    pip \
    setuptools \
    wheel

WORKDIR /project

RUN useradd -m -r user && \
    chown user /project

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY . .

USER user

ARG GIT_HASH
ENV GIT_HASH=${GIT_HASH:-dev}

ENTRYPOINT ["/tini", "--"]

# 💥 here you need to specify your command 💥
CMD ["gunicorn", "app.wsgi", "-b", "0.0.0.0:8080"]

Containerize a Python application with poetry

With poetry the dockerfile is slightly different, we need to install poetry, copy the poetry's files and then run poetry install --no-dev.

An important note: we don't really care about creating a virtual environment inside the image, so we install the dependencies globally.

# 💥 change the Python version with your version 💥
FROM python:3.8.3-slim-buster

ENV TINI_VERSION="v0.19.0"
ENV PIP_NO_CACHE_DIR=True
ENV POETRY_VIRTUALENVS_CREATE=False

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini

RUN pip install -U \
    pip \
    setuptools \
    wheel \
    poetry

WORKDIR /project

RUN useradd -m -r user && \
    chown user /project

COPY poetry.lock pyproject.toml ./
RUN poetry install --no-dev && \
    rm -rf ~/.cache/pypoetry/{cache,artifacts}

COPY . .

USER user

ARG GIT_HASH
ENV GIT_HASH=${GIT_HASH:-dev}

ENTRYPOINT ["/tini", "--"]

# 💥 here you need to specify your command 💥
CMD ["gunicorn", "app.wsgi", "-b", "0.0.0.0:8080"]

Build the docker image

If you cloned the repo locally you can build a docker image with this command

docker build -t django-app -f Dockerfile.requirements .

The -f flag specifies the dockerfile we want to use, for poetry we need to specify a different file

docker build -t django-app -f Dockerfile.poetry .

Remember that if you rename one of the files to Dockerfile then you can omit -f completely

docker build -t django-app .

One last thing, we can pass the GIT_HASH value using build-arg, this is not really needed locally, but is extremely useful inside a CI/CD.

export GIT_HASH = $(git rev-parse --short HEAD)
docker build --build-arg GIT_HASH=${GIT_HASH} -t django-app -f Dockerfile.poetry .

Run the docker image

Once the image is built we can run it with

docker run -it --rm -p 8080:8080 django-app

and then test the container with curl http://localhost:8080/healthz.

Push the docker image

This is a bonus step, but it shows you how to push a docker image to a registry.

In this case I'll use the docker hub, but the process is the same with any registry. Just remember that the docker image name (-t my-image-name) would change depending on the registry.

docker -t django-app pybootcamp/django-app:poetry

Once the image is tagged properly we can push it to the registry

docker push pybootcamp/django-app:poetry

Here you can find the docker image that I just published.

What's next?

You can start using these Dockerfiles inside your projects, ideally only a small number of changes are necessary.

Refactoring Dockerfiles is also important!

If you use the same dockerfile for multiple repositories you could consider extracting a base docker image.

For example we can have a base docker image tagged as python-base:3.8.3 created with the Dockerfile below

# a base docker image used across different projects
FROM python:3.8.3-slim-buster

ENV TINI_VERSION="v0.19.0"
ENV PIP_NO_CACHE_DIR=True
ENV POETRY_VIRTUALENVS_CREATE=False

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini

RUN pip install -U \
    pip \
    setuptools \
    wheel \
    poetry

WORKDIR /project

RUN useradd -m -r user && \
    chown user /project

ENTRYPOINT ["/tini", "--"]

Then in your project specific Dockerfile you can use the image as a base

FROM python-base:3.8.3

COPY poetry.lock pyproject.toml ./
RUN poetry install --no-dev && \
    rm -rf ~/.cache/pypoetry/{cache,artifacts}

COPY . .

USER user

ARG GIT_HASH
ENV GIT_HASH=${GIT_HASH:-dev}

CMD ["gunicorn", "app.wsgi", "-b", "0.0.0.0:8080"]

Useful resources

Boost your Python and DevOps skills

Get great content on Python, DevOps and cloud architecture.

You don't like spam? Neither do I!
And if you don't like what I share you can always opt-out.