Deploying Django application on DO app platform

Deploying Django application on DO app platform

March 5, 2024 | permanent

DevOps #

tags
DO, Apps Platform, Django, Docker

Django #

Example s #

  1. file:~/Code/personal/apps/e-invoicing/Dockerfile
    FROM debian:bullseye-slim
    
    # install python 3 and pip
    RUN apt-get update
    RUN apt-get install python3 -y
    RUN apt-get install virtualenv -y
    RUN apt-get install gettext -y
    RUN apt install python3-pip -y && pip3 install --upgrade pip
    
    # make python 3 default python environment
    RUN alias python=python3
    
    
    ENV PYTHONDONTWRITEBYTECODE=1
    ENV PYTHONUNBUFFERED=1
    
    # copy project to container'
    WORKDIR /opt
    COPY . .
    
    RUN virtualenv -p python3 venv
    RUN /opt/venv/bin/pip install -r requirements.txt
    # RUN virtualenv -p python3 venv
    # RUN source venv/bin/activate
    # RUN pip install -r requirements.txt
    # # install Django and Gunicorn dependencies from our requirements file
    # RUN cd /workspace && pip install -r requirements.txt
    # RUN cd e_invoicing && /opt/venv/bin/python manage.py migrate
    # RUN cd e_invoicing && /opt/venv/bin/python manage.py makemessages -l ar
    
    RUN chmod ug+x /opt/start.sh
    
    # CMD specifies the command to execute to start the server running.
    EXPOSE 8020
    STOPSIGNAL SIGTERM
    CMD ["/opt/start.sh"]
    
    start.sh
    #!/bin/bash
    # keep run commands here to avoid env variables problem
    
    cd /opt/e_invoicing
    /opt/venv/bin/python manage.py makemessages -l ar
    /opt/venv/bin/python manage.py compilemessages
    /opt/venv/bin/python manage.py migrate
    echo Starting Gunicorn.
    exec /opt/venv/bin/gunicorn --reload e_invoicing.wsgi:application \
        --bind 0.0.0.0:8020
    
  2. file:~/Code/personal/apps/product-inference/Dockerfile

Django with PostgreSQL and WhiteNoise #

Dockerfile #

file:~/Code/work/apps/projectx/Dockerfile

# Use an official Python runtime based on Debian 10 "buster" as a parent image.
FROM python:3.8.1-slim-buster

# Add user that will be used in the container.
RUN useradd django

# Port used by this container to serve HTTP.
EXPOSE 8000

# Set environment variables.
# 1. Force Python stdout and stderr streams to be unbuffered.
# 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE"
#    command.
ENV PYTHONUNBUFFERED=1 \
    PORT=8000

# Install system packages required by Django and Django.
RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
    build-essential \
    libpq-dev \
    libmariadbclient-dev \
    libjpeg62-turbo-dev \
    zlib1g-dev \
    libwebp-dev \
 && rm -rf /var/lib/apt/lists/*

# Install the application server.
RUN pip install "gunicorn==20.0.4"

# Install the project requirements.
COPY requirements.txt /
RUN pip install -r /requirements.txt

# Use /app folder as a directory where the source code is stored.
WORKDIR /app

# Set this directory to be owned by the "django" user. This Django project
# uses SQLite, the folder needs to be owned by the user that
# will be writing to the database file.
RUN chown django:django /app

# Copy the source code of the project into the container.
COPY --chown=django:django . .

WORKDIR /app/projectx

# Use user "django" to run the build commands below and the server itself.
USER django

# Collect static files.
RUN python manage.py collectstatic --noinput --clear

# Runtime command that executes when "docker run" is called, it does the
# following:
#   1. Migrate the database.
#   2. Start the application server.
# WARNING:
#   Migrating database at the same time as starting the server IS NOT THE BEST
#   PRACTICE. The database should be migrated manually or using the release
#   phase facilities of your hosting platform. This is used only so the
#   Django instance can be started with a simple "docker run" command.
CMD set -xe; python manage.py migrate --noinput; gunicorn projectx.wsgi:application

Django settings #

file:~/Code/work/apps/projectx/projectx/projectx/settings.py

from pathlib import Path
# ******* additions *******
import os
import sys
import dj_database_url

# ******* additions end *******

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-kds(kpr9af7kx46*p#f-76mm($r=ef1*eqv2r$i8pmnuoczmhj"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # authored
    "app",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    # ******* additions *******
    # to serve static files from container with whitenoise
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ******* end additions *******
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "projectx.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "projectx.wsgi.application"


# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/

STATIC_URL = "static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# ******* additions *******
STATIC_ROOT = os.path.join(BASE_DIR, "collect_static")
STATIC_URL = "/static/"


DEVELOPMENT_MODE = os.getenv("DEVELOPMENT_MODE", "off") == "on"

if DEVELOPMENT_MODE is True:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
        }
    }
elif len(sys.argv) > 0 and sys.argv[1] != 'collectstatic':
    DEBUG = os.getenv("DJANGO_DEBUG", "off") == "on"
    # because collectstatic is run while building the image
    if os.getenv("DATABASE_URL", None) is None:
        raise Exception("DATABASE_URL environment variable not defined")
    DATABASES = {
        "default": dj_database_url.parse(os.environ.get("DATABASE_URL")),
    }
    ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(";")
    CSRF_TRUSTED_ORIGINS = os.getenv("DJANGO_CSRF_TRUSTED_ORIGINS", "*").split(";")

 # ******* additions end *******

Configurations at App Platform #

DATABASE_URL=${db.DATABASE_URL}
DJANGO_DEBUG=off # default is off
DJANGO_CSRF_TRUSTED_ORIGINS=${APP_DOMAIN}
DJANGO_ALLOWED_HOSTS=${APP_DOMAIN}

Django with PostgreSQL, WhiteNoise and (S3 or DO Spaces) #

Wagtail #

TODO #

  1. file:~/Code/personal/cmss/wagtail_in_container/Dockerfile
    # Use an official Python runtime based on Debian 10 "buster" as a parent image.
    FROM python:3.8.1-slim-buster
    
    # Add user that will be used in the container.
    RUN useradd wagtail
    
    # Port used by this container to serve HTTP.
    EXPOSE 8000
    
    # Set environment variables.
    # 1. Force Python stdout and stderr streams to be unbuffered.
    # 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE"
    #    command.
    ENV PYTHONUNBUFFERED=1 \
        PORT=8000
    
    # Install system packages required by Wagtail and Django.
    RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
        build-essential \
        libpq-dev \
        libmariadbclient-dev \
        libjpeg62-turbo-dev \
        zlib1g-dev \
        libwebp-dev \
     && rm -rf /var/lib/apt/lists/*
    
    # Install the application server.
    RUN pip install "gunicorn==20.0.4"
    
    # Install the project requirements.
    COPY requirements.txt /
    RUN pip install -r /requirements.txt
    
    # Use /app folder as a directory where the source code is stored.
    WORKDIR /app
    
    # Set this directory to be owned by the "wagtail" user. This Wagtail project
    # uses SQLite, the folder needs to be owned by the user that
    # will be writing to the database file.
    RUN chown wagtail:wagtail /app
    
    # Copy the source code of the project into the container.
    COPY --chown=wagtail:wagtail . .
    
    # Use user "wagtail" to run the build commands below and the server itself.
    USER wagtail
    
    # Collect static files.
    RUN python manage.py collectstatic --noinput --clear
    
    # Runtime command that executes when "docker run" is called, it does the
    # following:
    #   1. Migrate the database.
    #   2. Start the application server.
    # WARNING:
    #   Migrating database at the same time as starting the server IS NOT THE BEST
    #   PRACTICE. The database should be migrated manually or using the release
    #   phase facilities of your hosting platform. This is used only so the
    #   Wagtail instance can be started with a simple "docker run" command.
    CMD set -xe; python manage.py migrate --noinput; gunicorn mysite.wsgi:application
    

Celery worker #

TODO #

Connecting Django or Wagtail to Databases from container #

When a database is attached to the app container, an environment variable DATABASE_URL will be added automatically. DATABASE_URL=$invoice-prod-cluster.DATABASE_URL # $database-name.DATABASE_URL

This injected connection string can be converted into Django setting’s database dictionary using dj-database-url app.

This can be changed to point to any connection string DATABASE_URL=sqlite:////full/path/to/your/database/file.sqlite Relative path example DATABASE_URL=sqlite://./db.sqlite3

examples of URL schemas support by dj-database-url

Engine	Django Backend	URL
PostgreSQL	django.db.backends.postgresql [1]	postgres://USER:PASSWORD@HOST:PORT/NAME [2]
PostGIS	django.contrib.gis.db.backends.postgis	postgis://USER:PASSWORD@HOST:PORT/NAME
MSSQL	sql_server.pyodbc	mssql://USER:PASSWORD@HOST:PORT/NAME
MSSQL [5]	mssql	mssqlms://USER:PASSWORD@HOST:PORT/NAME
MySQL	django.db.backends.mysql	mysql://USER:PASSWORD@HOST:PORT/NAME
MySQL (GIS)	django.contrib.gis.db.backends.mysql	mysqlgis://USER:PASSWORD@HOST:PORT/NAME
SQLite	django.db.backends.sqlite3	sqlite:///PATH [3]
SpatiaLite	django.contrib.gis.db.backends.spatialite	spatialite:///PATH [3]
Oracle	django.db.backends.oracle	oracle://USER:PASSWORD@HOST:PORT/NAME [4]
Oracle (GIS)	django.contrib.gis.db.backends.oracle	oraclegis://USER:PASSWORD@HOST:PORT/NAME
Redshift	django_redshift_backend	redshift://USER:PASSWORD@HOST:PORT/NAME
CockroachDB	django_cockroachdb	cockroach://USER:PASSWORD@HOST:PORT/NAME
Timescale [6]	timescale.db.backends.postgresql	timescale://USER:PASSWORD@HOST:PORT/NAME
Timescale (GIS) [6]	timescale.db.backend.postgis	timescalegis://USER:PASSWORD@HOST:PORT/NAME


No notes link to this note