A dockerized django server that I use in personal projects as a backend.
NOTE: diagram made with https://draw.io
- Nginx:
- Acts as a fast and lightweight reverse proxy
- Provides HTTPS support through Let's Encrypt for free
- Serves Django application static files (no need for WhiteNoise)
- Gunicorn
- Python WSGI-to-HTTP Server for UNIX
- Manages Django application thread pool
- PostgreSQL
- SQL compliant database with Django community support
- Redis
- PostgreSQL request caching through Django for UNIX
-
Django application caches the entire session context in Redis instead of using PostgreSQL for write-though persistent sessions. Session context cache misses are currently only applicable for the Django admin application, and therefore unlikely. To enable persistent sessions, uncomment
'django.contrib.sessions'
inINSTALLED_APPS
fordjango/settings/common.py
and changeSESSION_ENGINE
todjango.contrib.sessions.backends.cached_db
(https://docs.djangoproject.com/en/dev/topics/http/sessions/#configuring-sessions) -
Docker production design splits the internal Docker network into a fontend (Nginx) and backend (PostgreSQL & Redis) with the Django container serving as the link between the two for better Docker container isolation.
-
Redis is configured to not perform database snapshotting since a cache miss will not cause any current Django application logic issues (https://redis.io/topics/persistence)
-
All sensitive production configuration files are stored in a directory called
secrets
which is not tracked by Git. See:Application Secrets
README section below for more information. -
All production Docker containers are running as non-
root
users. Only the Nginx and Django containers must share the same user/group ID in order to share a Docker volume containing Django's static files to be served by Nginx.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# host setup
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y python3 python3-pip
python3 -m pip install --user pipenv
echo 'export PATH="${HOME}/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# remove snapd
sudo apt autoremove --purge snapd gnome-software-plugin-snap
sudo rm -rf /var/cache/snapd/
sudo systemctl daemon-reload
rm -rf ~/snap
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# development environment setup
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sudo apt-get install -y \
libpq-dev \
python3-dev \
build-essential \
python3-setuptools # psycopg2 (Python postgresql) dependencies
cd django
pipenv install --dev
pipenv shell # start virtualenv shell
export DJANGO_SETTINGS_MODULE=settings.development # set django settings module
rm -rf __dev-* # remove old dev files
python manage.py collectstatic --no-input # recollect static files
python manage.py migrate # setup database schema
python manage.py runserver 0.0.0.0:8000 # spin up django app
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# MISC development commands
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
python manage.py createsuperuser # add test admin user to database
python manage.py loaddata app_whoami.json # load JSON fixture (takes a while)
python manage.py flush # drop all data in each DB table
exit # exit virtual environment
-
MaxMind account creation:
-
Generating a new MaxMind license key:
- Login in and browse to Services > My License Key
- Create a new license key and save the key to
secrets/geoip.key
-
Getting GeoIP Lite direct download URLs:
- Browse to Account Summary > Download Databases
- Copy permalinks for needed CSV formatted database files
- Update the
URLS
variable indjango/app_whoami/fixtures/update.py
as needed
-
Updating django JSON fixture file
app_whoami.json
:cd django # enter project directory pipenv shell # start virtualenv shell cd app_whoami/fixtures # enter fixtures directory ./update.py -k ../../../secrets/geoip.key # generate new JSON fixture
-
Loading Django JSON fixture
app_whoami.json
into DB:-
DEVELOPMENT:
-
Connect to the DB:
cd django # enter project directory pipenv shell # start virtualenv shell python manage.py dbshell # start a DB SQL shell
-
-- If Django DB schema has not changed -- remove old table data:
SELECT name FROM sqlite_master WHERE name LIKE '%whoami%'; -- get tables DELETE FROM <table_name>; -- drop table data .exit -- exit db connection
-
-- If Django DB schema has changed -- delete tables:
SELECT name FROM sqlite_master WHERE name LIKE '%whoami%'; -- get tables DROP TABLE <table_name>; -- drop table .exit -- exit db connection
-
Import the new fixtures:
python manage.py migrate # re-create any broken tables python manage.py loaddata app_whoami.json # load JSON fixture (takes a while) exit # exit virtual environment
-
-
PRODUCTION:
-
Connect to the DB:
sudo systemctl start web # make sure django app is running sudo docker cp app_whoami.json django:/tmp/app_whoami.json # copy fixtures into container sudo docker exec -it django /bin/bash # get a bash shell in django container cd /app # navigate to project directory python manage.py dbshell # start a DB SQL shell
-
-- If Django DB schema has not changed -- remove old table data:
SELECT tablename FROM pg_catalog.pg_tables WHERE tablename LIKE '%whoami%'; -- get tables DELETE FROM <table_name>; -- drop table data \q -- exit db connection
-
-- If Django DB schema has changed -- delete tables:
SELECT tablename FROM pg_catalog.pg_tables WHERE tablename LIKE '%whoami%'; -- get tables DROP TABLE <table_name>; -- drop table data \q -- exit db connection
-
Import the new fixtures:
python manage.py migrate # re-create any broken tables python manage.py loaddata /tmp/app_whoami.json # load JSON fixture (takes a while) exit # exit container shell
-
-
-
Build application images:
# !!! snapshot current SQL db: source secrets/postgres.env && \ sudo docker-compose -f docker/docker-compose.yml exec postgres pg_dumpall -U $POSTGRES_USER > dump.sql sudo systemctl stop web # stop apps sudo docker rmi $(sudo docker images -aq) # remove apps sudo docker build --tag app_nginx -f docker/app_nginx.Dockerfile . # rebuild nginx sudo docker build --tag app_redis -f docker/app_redis.Dockerfile . # rebuild redis sudo docker build --tag app_django -f docker/app_django.Dockerfile . # rebuild django sudo docker build --tag app_postgres -f docker/app_postgres.Dockerfile . # rebuild db # !!! restore SQL db snapshot: sudo docker volume rm docker_postgres_data # remove old db sudo systemctl start web # start apps sudo docker cp dump.sql postgres:/tmp/dump.sql # copy snapshot into container source secrets/postgres.env && \ sudo docker-compose -f docker/docker-compose.yml exec -T postgres psql -U $POSTGRES_USER -d $POSTGRES_DB < dump.sql
-
Install docker-compose
web
service:-
Create a
/etc/systemd/system/web.service
file with the following content: (NOTE: replace<path to docker-compose.yml>
below with host system's path):[Unit] Description=Docker Compose App Service Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/bin/docker-compose -f <path to docker/docker-compose.yml> up -d ExecStop=/usr/local/bin/docker-compose -f <path to docker/docker-compose.yml> down TimeoutStartSec=0 [Install] WantedBy=multi-user.target
-
Install the service:
sudo systemctl enable web
-
-
Connecting to PostgreSQL DB:
sudo docker exec -it django /bin/bash # get a bash shell in django container cd /app # navigate to project directory python manage.py dbshell # start a DB shell
-
Connecting to Redis DB:
sudo docker exec -it redis /bin/bash # get a bash shell in redis container redis-cli --pass $REDIS_PASS # start a DB shell
-
Helpful production debugging commands:
# test bring up all the services sudo docker-compose -f docker/docker-compose.yml up -d # stop all running services sudo docker-compose -f docker/docker-compose.yml down # stop all running containers sudo docker stop $(sudo docker ps -aq) # delete all containers sudo docker rm $(sudo docker ps -aq) # delete all docker volumes sudo docker volume rm $(sudo docker volume ls -q) # delete all docker images sudo docker rmi $(sudo docker images -aq) # spawn a bash shell in a running container sudo docker exec -it <container_name> /bin/bash # create a standalone container from image with bash as entrypoint sudo docker run -p 80:8080 -p 443:4443 --env-file secrets/nginx.env -it --entrypoint /bin/bash <image_name> -s
-
Google Domains with Dynamic DNS in pfsense:
-
Nginx TLS configuration/security resources:
- https://gist.github.com/nrollr/9a39bb636a820fb97eec2ed85e473d38
- https://wiki.mozilla.org/Security/Server_Side_TLS
- https://github.com/trimstray/nginx-admins-handbook/blob/master/doc/RULES.md
- https://ssl-config.mozilla.org/
- [TEST TLS]: https://www.ssllabs.com/ssltest/analyze.html?d=arey.dev&hideResults=on
-
geoip.key
: Contains MaxMind account license key for GeoIPLite2 database offline downloads -
app.env
: Django Docker application container environmental variables-
import django.core.management.utils django.core.management.utils.get_random_secret_key()
-
postgres.env
: PostgreSQL Docker container environmental variables -
redis.env
: Redis environmental variables fordjango-redis
Django plugin in the Django Docker container -
redis.password.conf
: Redis default user password usingrequirepass
config option.- NOTE: password must match the
REDIS_PASS
value inredis.env
- NOTE: password must match the
-
nginx.env
: Nginx environmental variables to manage LetsEncryptcertbot
tool for TLS certificationsCERT_DOMAIN
CERT_RENEW_DELAY
CERT_RSA_KEY_SIZE
CERT_EMAIL
CERT_CREATE_FLAGS
: Used forcertbot certonly
commandCERT_RENEW_FLAGS
: Used forcertbot renew
command