Skip to content

Getting Started with Python Django

A live example is available at https://django.nullstone.dev.

This quickstart launches a Python Django web application to AWS via Nullstone. It also configures a local development environment using Docker that works identical to production, but with debugging enabled.

This quickstart contains a walkthrough for generating a Django app. A working example is available to fork at nullstone-io/django-quickstart.

TIP

This quickstart launches an empty Django project to Nullstone. After this guide, refer to the official Writing your first Django app to create a full Django app.

Create Django project

Install Django

shell
pip install django
pip install django

Create Project

In this example, we are going to create a django project named app in the current directory.

shell
django-admin startproject app .
django-admin startproject app .

This will create the following project structure:

shell
.
├── app
   ├── __init__.py
   ├── asgi.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py
└── manage.py
.
├── app
   ├── __init__.py
   ├── asgi.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py
└── manage.py

Initialize dependencies

We need to add core dependencies for our new django webapp.

shell
cat <<EOF > requirements.txt
django ~= 4.0
EOF
cat <<EOF > requirements.txt
django ~= 4.0
EOF

Now, install the dependencies:

shell
pip install -r requirements.txt
pip install -r requirements.txt

Configure Datastores

A Django app is not very interesting without a database. In this section, you will configure additional datastores.

Postgresql

To add postgresql, you need to update your application settings.

To see all code changes needed to add postgresql to your app, view the commit in the django quickstart on GitHub here.

  1. Add dj-database-url and psycopg2-binary to requirements.txt.
  2. pip install -r requirements.txt
  3. Configure settings.py to use DATABASE_URL environment variable via dj_database_url package.
python
import dj_database_url

DATABASES = {
  'default': dj_database_url.config(conn_max_age=600),
}
import dj_database_url

DATABASES = {
  'default': dj_database_url.config(conn_max_age=600),
}

Prepare for Local

Configure docker locally

In your project's root directory, create a file named docker-compose.yml with these contents:

yaml
version: '3.8'

services:
  app:
    image: nullstone/django:local
    ports:
      - "9000:9000"
    environment:
      - NULLSTONE_ENV=local
      - POSTGRES_URL=postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
    volumes:
      - .:/app
      - packages:/usr/local/lib/python3.9/site-packages
  
  db:
    image: "postgres:13"
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=app

volumes:
   packages: {}
version: '3.8'

services:
  app:
    image: nullstone/django:local
    ports:
      - "9000:9000"
    environment:
      - NULLSTONE_ENV=local
      - POSTGRES_URL=postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
    volumes:
      - .:/app
      - packages:/usr/local/lib/python3.9/site-packages
  
  db:
    image: "postgres:13"
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=app

volumes:
   packages: {}

Let's start our app locally.

shell
docker compose up
docker compose up

Visit http://localhost:9000.

Update dependencies

As you add dependencies to your application, ensure that there is an entry for the dependency in requirements.txt. Then, restart your docker container with docker compose up or docker compose restart.

Prepare for Production

Before deploying a Django app to production, there are changes you need to make to your application settings.

Update application settings

Open up settings.py and perform the following steps.

  1. Add an import for os and sys.
  2. Configure Django to load SECRET_KEY environment variable.
  3. Configure Django to load DJANGO_DEBUG environment variable so that production runs without debug information.
  4. Configure Django to log to stdout/stderr and use DJANGO_LOGLEVEL for configuring the log level.
  5. Configure Django to load ALLOWED_HOSTS dynamically from environment variables. Nullstone will inject NULLSTONE_PUBLIC_HOSTS, NULLSTONE_PRIVATE_HOSTS, and ECS_PRIVATE_IPS environment variables. This ensures that requests made to your Django application are secure.
python
from pathlib import Path
import os
import dj_database_url
import sys

# 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.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', default='django-insecure-9xcck4f4#^c*6lrpkv(=5#m93mbh0(xj)z_z&+3!8&tm^zq*-q')

# SECURITY WARNING: don't run with debug turned on in production!
# DEBUG = True
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'

LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper()

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'level': LOGLEVEL,
            'class': 'logging.StreamHandler',
            'stream': sys.stdout,
            'formatter': 'verbose'
        },
    },
    'loggers': {
        '': {
            'handlers': ['console'],
            'level': LOGLEVEL,
            'propagate': True,
        },
    },
}

ALLOWED_HOSTS = []
NULLSTONE_PUBLIC_HOSTS = os.environ.get('NULLSTONE_PUBLIC_HOSTS')
if NULLSTONE_PUBLIC_HOSTS:
    ALLOWED_HOSTS += NULLSTONE_PUBLIC_HOSTS.split(',')
NULLSTONE_PRIVATE_HOSTS = os.environ.get('NULLSTONE_PRIVATE_HOSTS')
if NULLSTONE_PRIVATE_HOSTS:
    ALLOWED_HOSTS += NULLSTONE_PRIVATE_HOSTS.split(',')
ECS_PRIVATE_IPS = os.environ.get('ECS_PRIVATE_IPS')
if ECS_PRIVATE_IPS:
    ALLOWED_HOSTS += ECS_PRIVATE_IPS.split(',')
from pathlib import Path
import os
import dj_database_url
import sys

# 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.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', default='django-insecure-9xcck4f4#^c*6lrpkv(=5#m93mbh0(xj)z_z&+3!8&tm^zq*-q')

# SECURITY WARNING: don't run with debug turned on in production!
# DEBUG = True
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'

LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper()

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'level': LOGLEVEL,
            'class': 'logging.StreamHandler',
            'stream': sys.stdout,
            'formatter': 'verbose'
        },
    },
    'loggers': {
        '': {
            'handlers': ['console'],
            'level': LOGLEVEL,
            'propagate': True,
        },
    },
}

ALLOWED_HOSTS = []
NULLSTONE_PUBLIC_HOSTS = os.environ.get('NULLSTONE_PUBLIC_HOSTS')
if NULLSTONE_PUBLIC_HOSTS:
    ALLOWED_HOSTS += NULLSTONE_PUBLIC_HOSTS.split(',')
NULLSTONE_PRIVATE_HOSTS = os.environ.get('NULLSTONE_PRIVATE_HOSTS')
if NULLSTONE_PRIVATE_HOSTS:
    ALLOWED_HOSTS += NULLSTONE_PRIVATE_HOSTS.split(',')
ECS_PRIVATE_IPS = os.environ.get('ECS_PRIVATE_IPS')
if ECS_PRIVATE_IPS:
    ALLOWED_HOSTS += ECS_PRIVATE_IPS.split(',')

Dockerize

Nullstone provides a base Django image that is ready for production. The only thing you need to do is create a Dockerfile.

WARNING

If you chose a different project name than app when running django-admin startapp, add ENV WSGI_APP=<project-name>.wsgi:application to the Dockerfile.

docker
FROM nullstone/django

COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
FROM nullstone/django

COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

Launch to Nullstone

Create app

When launching to Nullstone, we're going to be creating an app in the Nullstone UI and attaching capabilities that automatically configure our app. Follow these steps in the Nullstone UI.

  1. Create an application.
    • Name: In this example, we're naming our app django-quickstart
    • Framework: django
    • App Type: Container
  2. From the Domains tab for the application, add a subdomain. (This will automatically attach a load balancer capability)
  3. From the Capabilities tab for the application, add a capability named SECRET_KEY for Python Cookies. (This will enable secure encryption of cookies)

Create postgresql datastore

  1. Create a datastore - RDS Postgres Cluster
  2. Visit your application created in the previous step.
  3. In the Datastores tab, add the datastore you just created.

Provision

Our application is ready to launch. Click "Launch" through the UI or issue up through the CLI.

shell
nullstone up --wait --app=django-quickstart --env=dev
nullstone up --wait --app=django-quickstart --env=dev

Build

Once your application is provisioned, you may build and deploy your app.

You can name your image whatever you want, just remember this image name for the deploy step. In this example, we are using an image name of django-app.

shell
docker build -t django-app .
docker build -t django-app .

Deploy

Now, issue launch to push your docker image and deploy the service with a new version.

shell
nullstone launch --source=django-app --app=django-quickstart --env=dev
nullstone launch --source=django-app --app=django-quickstart --env=dev

Troubleshooting

No module named ...

If you specify something other than app when running django-admin startproject, you will receive an error like this when deploying to production.

shell
ModuleNotFoundError: No module named 'app'
ModuleNotFoundError: No module named 'app'

This is because the base docker image is encoded to use app/wsgi.py to load your application in the production environment. To resolve this, change your WSGI_APP in your Dockerfile to use the correct project name.

docker
ENV WSGI_APP=<project-name>.wsgi:application
ENV WSGI_APP=<project-name>.wsgi:application

You don't need to make any changes for local because this project is encoded in manage.py.

Auto-versioning

When pushing your image, Nullstone performs auto-versioning if you are in a git-tracked directory. Nullstone selects the short commit SHA (a unique 8-character token) from the git repository to tag the docker image.

To use a manual version, issue launch with --version (this example uses 1.0.0).

shell
nullstone launch --source=django-app --app=django-quickstart --env=dev --version=1.0.0
nullstone launch --source=django-app --app=django-quickstart --env=dev --version=1.0.0

Version conflicts

Nullstone enforces version/image tag immutability for security reasons.

If you repeatedly push a new docker image without committing anything to git, you will receive an error message like this:

shell
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.

The easiest way to resolve this is to launch with an indexed version. The following uses the same commit sha, but with a -2 suffix to distinguish the image tag.

shell
nullstone launch --source=django-app --app=django-quickstart --env=dev --version=c3c7cd83-2
nullstone launch --source=django-app --app=django-quickstart --env=dev --version=c3c7cd83-2