A set of Django framework pre-deployment preparation mandatory steps based on best practices for security.
See: https://docs.djangoproject.com/en/5.2/howto/deployment/
It can be used as a TODO list template for any relevant project.
Note:
This guide uses an older version of environs for dependency conflict
resolution demonstration purposes.
- Install Gunicorn as a WSGI server
- Install Psycopg adapter to connect with a PostgreSQL database
- Install environs for environment variables management and conflict resolution demo
- Update DATABASES in settings file
- Install WhiteNoise for static files
- Create a
requirement.txtfile and freeze dependencies - Add a
.dockerignorefile with correct paths - Create a
.envfile - Update
.gitignorefile - Update ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, DEBUG, SECRET_KEY
python -m pip install gunicorn==20.1.0
python -m pip install "psycopg[binary]"==3.1.8
python -m pip install "environs[django]"==9.5.0
python -m pip install whitenoise==6.4.0The environs package is a Python library for parsing environment variables. It allows you to store configuration separate from your code, as per "The Twelve-Factor App" methodology.
There is also the alternative Django specific django-environ package.
# django_project/settings.py
from pathlib import Path
from environs import Env # -> Import here
env = Env() # -> Register here
env.read_env() # -> Activate hereA chance of package versions conflict occurrence in Python in high.
In case of older environs version there is a problem with
the marshmalow dependency.
When trying running the server there is a chance of the following error reported:
# ...
File ".../django_project/settings.py", line 14, in <module>
from environs import Env
File ".../.venv/lib/python3.13/site-packages/environs/__init__.py", line 58, in <module>
_SUPPORTS_LOAD_DEFAULT = ma.__version_info__ >= (3, 13)
^^^^^^^^^^^^^^^^^^^
AttributeError: module 'marshmallow' has no attribute '__version_info__'Welcome to Python version dependency hell.
-
Solution:
First find the version of the reported package and the dependency located in the last line is the output of the command:
pip show marshmallow # Name: marshmallow # Version: 4.1.0 # Summary: A lightweight library for converting complex datatypes # to and from native Python datatypes. # Home-page: https://github.com/marshmallow-code/marshmallow # Author: Steven Loria # Author-email: [email protected] # License: MIT # Location: /project-path/app-path/.venv/lib/python3.13/site-packages # Requires: packaging # Required-by: environs
- Open your
requirements.txtfile and change the marshmallow line from:
from marshmallow==4.1.0 to marshmallow==3.19.0
-
Save the
requirements.txtfile. -
Uninstall the current marshmallow version:
python -m pip uninstall marshmallow
- Install the downgraded dependency:
python -m pip install -r requirements.txt
- Run the server again:
python manage.py runserver
- If you want to automate reinstalling with freezing versions in the future, you can use:
pip freeze > requirements.txt - Open your
Make sure to use PostgreSQL in production and fallback to Sqlite driver in local development.
# django_project/settings.py
DATABASES = {
"default": env.dj_db_url("DATABASE_URL", default="sqlite:///db.sqlite3"),
}Add WhiteNoise above the build-in staticfiles app, middleware and default storage engine and after the default Security middleware.
# django_project/settings.py
INSTALLED_APPS = {
# ...
"whitenoise.runserver_nostatic", # -> Add here
"django.contrib.staticfiles",
# ...
}
MIDDLEWARE = [
# ...
"whitenoise.middleware.WhiteNoiseMiddleware",
# ...
]
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
# "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", # -> Django's default Static Files management
## Replace the above with the following:
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}Collect the static files and overwrite the older ones. Then restart the server:
python manage.py collectstatic
# You have requested to collect static files at the destination
# location as specified in your settings:
#
# # /path-to-project/path-to-app/staticfiles
#
# This will overwrite existing files!
# Are you sure you want to do this?
#
# Type 'yes' to continue, or 'no' to cancel:
yes
#
# 127 static files copied to '/path-to-project/path-to-app/staticfiles', 1 unmodified, 384 post-processed.
python manage.py runserverpython -m pip freeze > requirements.txtThe .env file is used to store sensitive variables and secrets
so we must make sure is excluded from deployment.
- The
.dockerignorefile:
.venv/
__pycache__/
*.sqlite3
.git
.env- The
.gitignorefile:
.venv/
__pycache__/
*.sqlite3
.envCreate the file to hold our sensitive information and custom variable values. This file must be never uploaded in any Version Control system, or deployment platform.
There should be NO spaces included in this file.
DEBUG=True
SECRET_KEY=The DEBUG setting is a boolean and must be never
set to True in a production environment. This will disable
the local server among other things and hide all sensitive
info and diagnostics that are exposed in localhost:8000/debug
by default.
Manage the variable value through the .env file so that
returns False in production and True in local development:
# django_project/settings.py
# DEBUG = True # -> Change this to the following:
DEBUG = env.bool("DEBUG", default=False)IMPORTANT: In case there was a previous commit in github or any other VCS online service, the SECRET_KEY MUST be regenerated, as its value can be tracked through VCS history. After the setup run again the local server to ensure everything is working with the new key.
- Regenerate a new key for the project and copy the new one from terminal:
python -c "import secrets; print(secrets.token_urlsafe())"
# new-generated-key- Paste the new key in case of previous commit, to the
.envfile without spaces:
DEBUG=True
SECRET_KEY=new-generated-key- Replace the value in settings:
# django_project/settings.py
# SECRET_KEY = "django-insecure-old-key" # -> Replace with the following:
SECRET_KEY = env.str("SECRET_KEY")Except DEBUG and SECRET_KEY settings, there are two more main
sensitive variables that must be secured for production:
ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS:
# django_project/settings.py
# ALLOWED_HOSTS = ["*"] # -> Allows access to any host. Change to:
ALLOWED_HOSTS = [ ".my-platform.dev", "localhost", "127.0.0.1" ]
CSRF_TRUSTED_ORIGINS = ["https://*.my-platform.dev"] # -> Add your platform hereFinally we can proceed with the actual deployment.