Skip to content

a-rey/docker_website

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Docker Django Server

A dockerized django server that I use in personal projects as a backend.

Architecture Notes:

NOTE: diagram made with https://draw.io

Architecture

  • Nginx:
  • 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

Design Notes:

  • 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' in INSTALLED_APPS for django/settings/common.py and change SESSION_ENGINE to django.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 Notes:

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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 GeoIP Database Management Notes:

  • 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 in django/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

Production Notes:

  • 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:

Application Secrets:

Application Resources: