This is a short introduction how to build a Django app with Poetry as a package manager and then make it work in a Docker container. Finally, there's a how-to for Heroku and an example app you can use as a reference.
Setting up Poetry
Installing poetry is very simple:
pip3 install --user poetry
Poetry uses the pyproject.toml
, a file based on
PEP-518, which is supposed
to be a unified way of specifying project meta information, build system,
dependencies, etc. However, each tool that uses this file can
add its own sections and the mess with Python virtual environments
and package managers continues.
Adding dependencies is as easy as:
poetry add Django
I'd recommend always switching into the venv by poetry shell
and then running ./manage.py runserver
.
Now, Poetry uses it's lock file poetry.lock
. This is different
from using plain old requirements.txt
for pip
, but it's on par
with - for example - Pipenv.
The advantage of using Poetry over virtualenv or Pipenv is the easy interface and the option to build and publish packages on PyPI.
So now we setup the project with Poetry as the package manager of choice and our Django app works locally without problems (hopefully).
gunicorn
gunicorn is a Python WSGI server that we'll use for the Docker image.
You can already install it with Poetry: poetry add --dev gunicorn
and then run your app using gunicorn project.wsgi
.
Configuring static files
Serving static files is a bit more complicated with gunicorn, but once we set up a few things, it'll help us create the Docker image.
In your settings.py
, configure static files like this:
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, '<module name>', 'static'),
)
Furthermore, you need to install and enable whitenoise:
poetry add --dev whitenoise
settings.py
:
MIDDLEWARE = [
# ...
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
Putting everything into Dockerfile
For reference, I will put an example Dockerfile here and then explain how it works.
FROM python:3
WORKDIR /usr/src/app
EXPOSE 8000
# Install poetry and deps
# Exporting to requirements.txt will avoid hassle with poetry venv
COPY pyproject.toml ./
COPY poetry.lock ./
RUN pip install poetry
RUN poetry export -f requirements.txt --output requirements.txt
RUN pip install -r requirements.txt
# Copy project
COPY manage.py manage.py
COPY <your app> <your app>
COPY <your project> <your project>
# Collect static files, pass dummy DJANGO_SECRET_KEY
# or else ./manage.py will fail
# since DJANGO_SECRET_KEY is not available during the build step
RUN DJANGO_SECRET_KEY=dummy ./manage.py collectstatic
# Run server
CMD [ "gunicorn", "-b", "0.0.0.0:8000", "<your project>.wsgi" ]
The steps are already somewhat explained with the comments. In more detail now.
We start with a base python3 image, setup a working directory for the
project source code and expose the port where gunicorn
will be running
(default is 8000).
FROM python:3
WORKDIR /usr/src/app
EXPOSE 8000
Then we copy files for Poetry, pyproject.toml
and poetry.lock
:
COPY pyproject.toml ./
COPY poetry.lock ./
After that, we install Poetry itself, use it to export our dependencies
from the lock file into requirements.txt
and then use pip
to install
those frozen dependencies:
RUN pip install poetry
RUN poetry export -f requirements.txt --output requirements.txt
RUN pip install -r requirements.txt
Now the environment is setup and we can copy the project source code (actually this step could happen earlier):
COPY manage.py manage.py
COPY <your app> <your app>
COPY <your project> <your project>
<your project>
is the root folder name and <your app>
is each app
inside your project.
Now the part about collecting static files from your apps.
RUN DJANGO_SECRET_KEY=dummy ./manage.py collectstatic
Since this is a build step, ./manage.py
cannot reach the environment
variables. They are only reachable once the app is running.
This SO question
sums the problem up. However, we don't need to generate random secret key
as in the accepted answer. What we can do is inside
this Dockerfile example.
Just export a dummy DJANGO_SECRET_KEY
before running ./manage.py collectstatic
so the script doesn't fail and everything is okay. Since this is a build step
that doesn't in any way interact with the users of your app, it's
perfectly fine to use any dummy value.
And finally we start up the web server:
CMD [ "gunicorn", "-b", "0.0.0.0:8000", "<your project>.wsgi" ]
Now build the image:
docker build -t project-name .
And run it:
docker run --rm -e DJANGO_SECRET_KEY="<secret key>" -p 8000:8000 project-name
Using Poetry on Heroku
For deploying your project on Heroku, you need a Procfile
in your
project's root directory, containing the command to run the server:
web: gunicorn project-name.wsgi
But since Heroku's heroku/python
buildpack only detects requirements.txt
or Pipfile
, we need some pre-processing to export Poetry's dependencies
into requirements.txt
.
This is done by a python-poetry-buildpack.
It needs to run before heroku/python
. This will export requirements.txt
for us, which can be then grabbed by heroku/python
and the deploy process
can continue as with pip or Pipenv.
Example app
Feel free to use my daylio-stats project as a reference for building your own cool stuff! Some of the steps described here were pretty difficult to find on the internet and make work.