Inicjalizacja projektu Django w kontenerze Docker-a

Listopad 28, 2023 | #docker , #django , #python

000001
010100
001100
100000

Jeśli chcesz szybko zainicjować nowy projekt Django od razu w kontenerze to możesz skorzystać z przepisu, który znajdziesz na awesome-compose/official-documentation-samples/django/ W moim artykule podążam za przedstawionymi tam instrukcjami ale jest kilka różnic.

  • Użyłem [[Wykorzystanie miejsca na dysku w pracy z Docker#Główne typy obrazów Docker, biorąc pod uwagę ich wielkość|lekkich obrazów alpine]] do utworzenia kontenerów z projektem Django oraz Postgresql
  • Dodałem użytkownika, który nie jest root-em (non root user)

Utwórzmy nowy katalog na potrzeby projektu Django ./project_django i wejdźmy do niego.

W katalogu projektu utworzymy kilka plików i katalogów. Docelowo struktura projektu będzie wyglądała następująco

project_django/
├── app/
│   ├── manage.py
│   └── project_name/
├── assets/
│   ├── static/
│   └── media/
├── .env
├── docker-compose.yml
├── Dockerfile
└── requirements.txt

Utwórzmy pliki i katalogi

touch .env Dockerfile requirements.txt docker-compose.yml
mkdir ./app && mkdir -p ./assets/static && mkdir -p ./assets/media

Plik .env zawiera kilka zmiennych przechowujących wrażliwe dane. Wyjątkiem jest tu USER_UID przechowujący identyfikator bieżącego użytkownika hosta. Zmienna ta jest nam potrzebna po to aby użytkownik w kontenerze Docker-a mógł zapisywać pliki w podmontowanych katalogach hosta.

Wartość UID można odczytać w systemie linuks wywołując

id -u
USER_UID=1000

# Django
# ------------------------------------------------------------------------------
DJANGO_SECRET_KEY=change-me

# PostgreSQL
# ------------------------------------------------------------------------------
POSTGRES_DB=dbname
POSTGRES_USER=dbuser
POSTGRES_PASSWORD=dbpass

Plik Dockerfile

FROM python:3.12.1-alpine3.19

# Zmienne środowiskowe wymagane przez Django
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Zdefiniowanie zmiennej przechowującej
# ścieżkę do katalogu domowego
ENV HOME=/src

# Domyślna nazwa użytkownika i grupy oraz wartości UID i GID
# Można je nadpisać przekazując odpowiednie parametry
ARG USERNAME=default
ARG GROUPNAME=$USERNAME
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Utworzenie grupy i użytkownika o tej samej nazwie
RUN addgroup -g ${USER_GID} ${GROUPNAME} \
    && adduser -D ${USERNAME} -G ${GROUPNAME} -u ${USER_UID} -h ${HOME} \
    && mkdir -p /etc/sudoers.d \
    && echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${USERNAME} \
    && chmod 0440 /etc/sudoers.d/${USERNAME}

# Instalacja zależności wymaganych przez Postgresql
RUN apk update \
    && apk add --no-cache \
    build-base \
    musl-dev \
    postgresql-dev \
    python3-dev \
    && pip install --upgrade pip

# Ustawienie katalogu roboczego na katalog domowy użytkownika
WORKDIR ${HOME}

# Skopiowanie pliku z zależnościami python i ich instalacha
COPY ./requirements.txt ${HOME}/requirements.txt
RUN pip install -r requirements.txt

# Skopiowanie kodu aplikacji do katalogu domowego
COPY ./app ${HOME}

# Utworzenie katalogów na pliki statyczne i media oraz
# zmiana właściciela a tym samym uprawnień do wszystkich plików
# w katalogu domowym i plików statycznych
RUN mkdir -p /assets/static \
  && mkdir -p /assets/collected_static \
  && mkdir -p /assets/media \
  && chown -R ${USERNAME}:${GROUPNAME} ${HOME} \
  && chown -R ${USERNAME}:${GROUPNAME} /assets

# Ustawienie użytkownika
USER ${USERNAME}

Plik requirements.txt

Django>=5.0
psycopg2

Plik docker-compose.yml

version: '3.8'
services:
  postgres:
    image: postgres:16.1-alpine3.19
    command: postgres -c log_statement=all
    volumes:
      - postgres_volume:/var/lib/postgresql/data
    networks:
      - internal_network
    ports:
      - "5432:5432"
    env_file:
      - ./.env
  django:
    build:
      context: .
      args:
        USER_UID: ${USER_UID:-1000}
    depends_on:
      - postgres
    command: python manage.py runserver 0.0.0.0:8000
    networks:
      - internal_network
    ports:
      - "8000:8000"
    env_file:
      - ./.env
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
    volumes:
      - ./app:/src
      - ./assets/static:/assets/static
      - ./assets/media:/assets/media

volumes:
  postgres_volume:

networks:
  internal_network:

Katalog ./app na hoście jest podpięty pod katalog /src wewnątrz kontenera. To w nim znajdą się pliki projektu, który zaraz zostanie wygenerowany.

Są też zbindowane dwa katalogi hosta z plikami statycznymi i mediami

Utworzenie projektu Django

docker compose run --rm django django-admin startproject project_name .

Powyższa instrukcja zbudowała obraz na podstawie pliku Dockerfile i stworzyła na jego podstawie kontener, w którym została odpalona komenda Django tworząca nowy projekt. W katalogu projektu zostały utworzone pliki projektu tj. plik manage.py i katalog project_name wraz z zawartością.

Ponieważ zdefiniowaliśmy użytkownika (non root) z uprawnieniami bieżącego użytkownika hosta nie musimy zmieniać uprawnień wygenerowanych plików.

Zmiany w konfiguracji projektu Django

Django oferuje ogromne możliwości konfiguracji, ale ten artykuł nie jest o setupowaniu Django - więc zmienimy tylko dwie rzeczy,

  1. Konfigurację bazy danych w ./app/project_name/settings.py w projekcie Django. W końcu zdecydowaliśmy się na Postgresa
import os

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': os.environ.get('POSTGRES_DB', ''),
        'USER': os.environ.get('POSTGRES_USER', ''),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''),
        'HOST': os.environ.get('POSTGRES_HOST', ''),
        'PORT': int(os.environ.get('POSTGRES_PORT', '')),
    }
}
  1. Ustawienia dotyczące plików statycznych i mediów.

w pliku ./app/project_name/settings.py

# Path /assets/static
STATICFILES_DIRS = (BASE_DIR.parent / "assets/static",)

STATICFILES_FINDERS = (
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
)
# Path /assets/collect_static
STATIC_ROOT = BASE_DIR.parent / "assets/collected_static"
STATIC_URL = "static/"

# Path /assets/media
MEDIA_ROOT = BASE_DIR.parent / "assets/media"
MEDIA_URL = "/media/"

I w pliku ./app/project_name/urls.py

from django.conf import settings
from django.conf.urls.static import static

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Uruchomienie projektu Django

Po uruchomieniu sprawdzamy czy w logach nie ma błędów.

docker compose up

Jeśli projekt działa pod adresem localhost:8000 oznacza to, że mamy poprawnie zainicjowany w kontenerze Docker nowy project Django, z którym od razu możemy pracować.

Upewnijmy się jeszcze, że katalogi ze plikami statycznymi i mediami zostały poprawnie podmontowane. W tym celu dodaj do katalogu ./assets/media jakiś obrazek np. o nazwie example.jpg i sprawdź czy jest widoczny pod adresem localhost:8000/media/example.jpg. Analogicznie zrób ze static-ami.

Akceptuję Ta strona zapisuje niewielkie pliki tekstowe, nazywane ciasteczkami (ang. cookies) na Twoim urządzeniu w celu lepszego dostosowania treści oraz dla celów statystycznych. Możesz wyłączyć możliwość ich zapisu, zmieniając ustawienia Twojej przeglądarki. Korzystanie z tej strony bez zmiany ustawień oznacza zgodę na przechowywanie cookies w Twoim urządzeniu.