Lecture: Dockerizing your Project

July 28, 2026, 4 p.m. (America/Los_Angeles)

In this lecture we "dockerize" our local development project in preparation for porting it to a VPS.

Lecture Preview

Content

Create myenv.env

An .env file is a good way to store secrets locally on your computer.

Never commit your .env files to github. This is why they are in the .gitignore list

Generate Local Secret

cd homepage
python manage.py shell

paste in the following text to generate your my_env.env file

from django.core.management.utils import get_random_secret_key  
key_string = get_random_secret_key()
template = '''DJANGO_SECRET_KEY='{key_string}'
DJANGO_DEBUG=True
MYSQL_ROOT_PASSWORD='<create-a-robust-pw-and-paste-here>'
MYSQL_DATABASE='<pick-a-database-name>'
MYSQL_USER='<create-a-random-username-and-paste-here>'
MYSQL_PASSWORD='<create-a-second-robust-pw-and-paste-here>'
'''
with open('../my_env.env','w') as f:
    s=template.format(key_string=key_string)
    f.write(s)

Only run this code once. Re-running it will overwrite your file and potentially lock you out of your own database.

Edit the remaining fields, using your favorite key generator or password manager to generate the required info.

What is Docker

make a new subidrectory named docker/

Install

see lecture 2

Docker images

Docker images run on your machine, often with administrative permissions. If you want to make your own image in order to control what software is built, do the following:

make a new file, docker/image_django/Dockerfile. Paste in the following text:

FROM python:3.14.2-slim-bookworm

ARG DEBIAN_FRONTEND=noninteractive

RUN apt update && apt install -y \
    libmariadb-dev \
    build-essential \
    default-libmysqlclient-dev \
    python3-dev \
    pkg-config \
    nano \
    iputils-ping \
    net-tools \
    iproute2

WORKDIR /build
COPY requirements.txt .
RUN pip install -r requirements.txt

Docker Images

Django Image

Next create a build script at docker/image_django/build. Paste in the following:

#!/usr/bin/bash
cp ../../requirements.txt ./ && \
docker build -t django_markdown . --progress plain && \
rm ./requirements.txt

then from docker/image_django/build run

chmod +x build
./build

Mariadb image

in docker/image_mariadb/ create Dockerfile

FROM mariadb:latest

ARG DEBIAN_FRONTEND=noninteractive

RUN apt update && apt install -y \
    mariadb-client

and create docker/image_mariadb/build

#!/usr/bin/bash
docker build -t my_maria_db .

then from docker/image_mariadb/build run

chmod +x build
./build

caddy image

create docker/image_caddy/Dockerfile

FROM caddy:2-alpine
RUN apk add --no-cache \
    bash

create docker/image_caddy/build

#!/usr/bin/bash
docker build -t django_caddy .

then from docker/image_caddy run

chmod +x build
./build

Docker Compose

Create your docker compose file

Create docker/docker-compose.yaml and paste in the following:

Note: you have to modify /path/to/your/markdown/source/files with your own path

services:
  django:
    hostname: django
    container_name: django
    # image: danb0b/django_markdown:latest
    image: django_markdown:latest
    env_file: "../my_env.env"
    volumes:
      - ../homepage:/homepage
      - /home/danaukes/repos/websites/danb0b.github.io:/source
      - ../collected_files:/files
      - ./log:/log
    working_dir: /homepage
    command: bash -c "./run"
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
        restart: true
        required: true
    networks:
    - local
  caddy:
    hostname: caddy
    container_name: caddy
    # image: danb0b/django_caddy:latest
    image: django_caddy:latest
    cap_add:
      - NET_ADMIN
    ports:
      - 8000:8000
      - 8001:8001
    volumes:
      - ./caddyconf:/etc/caddy
      - ./caddysite:/srv
      - ./caddydata:/data
      - ./caddyconfig:/config
      - ../collected_files:/files
    restart: unless-stopped
    networks:
      - local
  db:
    hostname: db
    container_name: db
    # image: danb0b/my_maria_db:latest
    image: my_maria_db:latest
    restart: unless-stopped
    volumes:
      - ./mysql:/var/lib/mysql
    env_file: "../my_env.env"
    networks:
    - local
    healthcheck:
      test: mariadb-admin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
      start_period: 5s
      interval: 5s
      timeout: 5s
      retries: 5
networks:
  local:
    driver: bridge

Caddy

Caddy is a web server that is easily configured, and can be easily set up to serve secure https pages with the LetsEncrypt service.

create a new file at docker/caddyconf/Caddyfile. Paste in the following:

:8000 {
    reverse_proxy {
        to django:8000
    }
}

:8001 {
    root * /files
    file_server
    log {
        output file /var/log/danaukes_files.log
        format json
    }
}

Update homepage/homepage/settings.py

below imports add:

import os

replace

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-m7hog_clla!4b0gx^-0i*aa^^9ub+9+#xz_pz=$f%orx)=cmj^'

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

ALLOWED_HOSTS = []

with

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'
ALLOWED_HOSTS = [
    '127.0.0.1',
    'localhost',
]

Replace

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

with

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ.get('MYSQL_DATABASE'),
        'USER': os.environ.get('MYSQL_USER'),
        'PASSWORD': os.environ.get('MYSQL_PASSWORD'),
        'HOST': 'db',
        'PORT': '3306',
        }
}

replace

STATIC_URL = 'static/'
STATIC_ROOT = '/home/danaukes/repos/django/django_homepage_public/collected_files'
STATICFILES_DIRS = [
    '/home/danaukes/repos/websites/danb0b.github.io/static',
    '/home/danaukes/repos/websites/danb0b.github.io/content',
    ]
# custom addition for defining the search path for markdown file imports
MARKDOWN_SOURCE_PATH = '/home/danaukes/repos/websites/danb0b.github.io/content'

with:

STATIC_URL = 'http://127.0.0.1:8001/'
STATIC_ROOT = '/files'
STATICFILES_DIRS = [
    '/source/static',
    '/source/content',
    ]

# custom addition for defining the search path for markdown file imports
MARKDOWN_SOURCE_PATH = '/source/content'

# Added to permit only logins when the site is found at the following domains
CSRF_TRUSTED_ORIGINS = ['http://127.0.0.1',]

Update .gitignore

make sure your gitignore file has a few new files. Replace it with the following:

**/__pycache__/*
**/migrations/*
.venv/*
*.env
homepage/db.sqlite3
docker/mysql/*
collected_files/*
docker/caddyconfig/*
docker/caddydata/*
docker/log/*
.vscode/settings.json

Update run

We will be using a server called gunicorn to host caddy. Replace the contents of homepage/run with:

#!/usr/bin/bash
# python3 manage.py runserver 0.0.0.0:8000
python -m gunicorn -w 4 --proxy-protocol auto -b 0.0.0.0:8000 --access-logfile '/log/gunicorn.log' homepage.wsgi:application

Create an import command

inside homepage/ create import:

#!/usr/bin/bash
python3 manage.py import_markdown

make it executible:

chmod +x import

add three new files

Add these files to your top-level project directory:

collect

#!/usr/bin/bash
docker exec -it django ./collect

import

#!/usr/bin/bash
docker exec -it django ./import

migrate

#!/usr/bin/bash
docker exec -it django ./migrate

make executible

chmod +x collect
chmod +x import
chmod +x migrate

Run

from your docker/ subfolder, run

docker compose up

In a separate window in the main project folder type:

docker exec -it django ./startup

follow the prompts to create a new superuser and import your markdown.

You have to do this again because you are using a new database for the first time

once complete, you can run:

./collect

to collect all static files

Git Repository

The branch reflecting these changes can be found here:

https://github.com/danb0b/django_homepage_public/tree/Lecture10-production-local

This Content is Locked

The rest of the text is available but you have to be signed in to view it. Please create an account or sign in to continue...