Skip to content

Instantly share code, notes, and snippets.

@linkdd
Last active February 5, 2024 11:21
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save linkdd/4aac2c2efc4a51af6ca4b05f395de728 to your computer and use it in GitHub Desktop.
Save linkdd/4aac2c2efc4a51af6ca4b05f395de728 to your computer and use it in GitHub Desktop.
Python Django Dockerfile

Python Django Dockerfile

When setting up a Python/Django project, I like to do a few things:

  • use poetry to manage dependencies and virtualenv
  • use poe to manage tasks (similar to node's scripts)
  • split the django settings.py into multiple files, with one per environment (similar to Elixir compile-time config)
  • use gunicorn and whitenoise to serve the application
  • use a multi-stage Dockerfile making heavy use of the docker layer cache to avoid running intensive steps needlessly
  • use django-tailwind and django-anymail
  • use python-decouple to manage configuration from .env or environment variables
from decouple import config
import multiprocessing
wsgi_app = "mydjangoproject.wsgi:application"
loglevel = config("LOG_LEVEL", default="warn")
workers = multiprocessing.cpu_count() * 2 + 1
bind = "0.0.0.0:8000"
[tool.poetry]
name = "mydjangoproject"
version = "0.1.0"
description = ""
authors = ["..."]
[tool.poetry.dependencies]
python = "^3.10"
python-decouple = "^3.6"
Django = "^4.0.3"
dj-database-url = "^0.5.0"
django-anymail = {extras = ["mailjet"], version = "^8.5"}
django-tailwind = "^3.1.1"
psycopg = "^3.0.9"
whitenoise = "^6.0.0"
gunicorn = "^20.1.0"
[tool.poetry.dev-dependencies]
poethepoet = "^0.13.1"
isort = "^5.10.1"
black = "^22.1.0"
pytest = "^7.1.1"
pytest-cov = "^3.0.0"
pytest-django = "^4.5.2"
pytest-env = "^0.6.2"
pytest-mock = "^3.7.0"
pytest-freezegun = "^0.4.2"
[tool.black]
use-tabs = false
[tool.isort]
profile = "black"
[tool.poe.tasks]
"ci:format" = "poetry run black ."
"ci:lint" = "poetry run black --check ."
"ci:django-check" = "poetry run python manage.py check"
"ci:test" = "poetry run pytest"
"db:superuser" = "poetry run python manage.py createsuperuser"
"db:migrate" = [
{ cmd = "poetry run python manage.py makemigrations"},
{ cmd = "poetry run python manage.py migrate"},
]
"css:deps" = "poetry run python manage.py tailwind install"
"css:build" = "poetry run python manage.py tailwind build"
"css:serve" = "poetry run python manage.py tailwind start"
"app:collectstatic" = "poetry run python manage.py collectstatic"
"app:serve" = "poetry run python manage.py runserver"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
## Python VENV configuration
FROM scratch AS context
ADD pyproject.toml poetry.lock /workspace/
## Source-tree
FROM scratch AS source-tree
COPY --from=context /workspace /workspace
ADD static/ /workspace/static/
ADD templates/ /workspace/templates/
ADD mydjangoproject/ /workspace/mydjangoproject/
ADD gunicorn.conf.py manage.py /workspace/
## Test suite
FROM scratch AS test-suite
ADD pytest.ini /workspace/pytest.ini
ADD tests/ /workspace/tests
## Python VENV production dependencies
FROM linksociety/nodepython:1.0.0-python3.10-node18 AS deps
COPY --from=context /workspace /workspace
WORKDIR /workspace
RUN poetry install --no-root --no-dev
## Python VENV development dependencies
FROM linksociety/nodepython:1.0.0-python3.10-node18 AS dev-deps
COPY --from=deps /workspace /workspace
WORKDIR /workspace
RUN poetry install --no-root
## CI steps
FROM linksociety/nodepython:1.0.0-python3.10-node18 AS ci
COPY --from=source-tree /workspace /workspace
COPY --from=dev-deps /workspace/.venv /workspace/.venv
COPY --from=test-suite /workspace/pytest.ini /workspace/pytest.ini
COPY --from=test-suite /workspace/tests /workspace/tests
WORKDIR /workspace
ENV MYDJANGOPROJECT_ENV "test"
RUN poetry run poe ci:lint
RUN poetry run poe ci:django-check
RUN poetry run poe ci:test
RUN poetry run poe css:deps
RUN poetry run poe css:build
RUN poetry run poe app:collectstatic
## Final artifact
FROM linksociety/poetry:3.10.4 AS runner
ARG BUILD_ENV="prod"
COPY --from=source-tree /workspace /workspace
COPY --from=deps /workspace/.venv /workspace/.venv
COPY --from=ci /workspace/dist /workspace/dist
WORKDIR /workspace
ADD docker/webserver-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENV MYDJANGOPROJECT_ENV "${BUILD_ENV}"
RUN poetry install --no-dev
EXPOSE 8000
ENV DJANGO_SECRET_KEY ""
ENTRYPOINT [ "/docker-entrypoint.sh" ]
from decouple import config
import sys
MYDJANGOPROJECT_ENV = config("MYDJANGOPROJECT_ENV", default="dev")
MYDJANGOPROJECT_SETTINGS_MODULE = f"mydjangoproject.settings.envs.{MYDJANGOPROJECT_ENV}"
try:
__import__(MYDJANGOPROJECT_SETTINGS_MODULE)
except ImportError as err:
print(err, file=sys.stderr)
sys.exit(1)
else:
mod = sys.modules[MYDJANGOPROJECT_SETTINGS_MODULE]
locals().update(mod.__dict__)
#!/usr/bin/env bash
if [ "$MYDJANGOPROJECT_ENV" = "prod" ]
then
echo ":: Starting production webserver"
poetry run gunicorn -c gunicorn.conf.py
else
echo ":: Starting development webserver"
poetry run python manage.py runserver "0.0.0.0:8000"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment