Wie man django in Entwicklung Containerisiert - via docker compose
Published: 2024-06-09 19:45:51

## Kontext
Des öfteren gibt es in der Entwicklung das Problem, dass es einen Fehler gibt, der nur auf einem Gerät auftaucht.

![It works on my machine meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ku0o6d8f3eb4tq4sapw.jpg)

Häufig liegt das daran, dass in den verschiedenen Entwicklungsumgebungen verschiedene Programmversionen und/oder Libraries installiert sind.

Entwickler A:
PostgreSQL 14.6
Python 3.7.11
Redis 7.4.2

Entwickler B:
PostgreSQL 14.6
Python 3.9.11
Redis 7.4.2

Wenn es jetzt einen Bug gibt, der in Python 3.7.11 Auftaucht, aber in Python 3.9.11 nicht - wird davon nur Entwickler A betroffen sein. 
Normalerweise ist es jedoch so, dass es sehr lange dauert herauszufinden dass der Bug aus den Versionsunterschieden stammt. Es ist also nicht sofort ersichtlich, dass wegen der unterschiedlichen Versionen der Bug auftritt. Alle Versionen und Libraries bei allen Entwicklern immer gleich zu halten ist schwierig bis unmöglich / sinnlos.
Außerdem ist es oft auch so, dass immer wieder neue Entwickler technisch aufgestellt werden müssen. Das kann teilweise mehrere Tage Arbeit bedeuten, damit nur eine Person alles in einen 'funktionierenden' Zustand bekommt.
Hier kommt jetzt docker compose & Containerisierung ins Spiel. Um verschiedene Applikations- und Libraryversionen zu vermeiden, und ebenso den Technischen onboarding - Prozess zu vereinfachen ist hier einiges möglich. Mit etwas DevOps wissen kann man `docker-compose.yml` & `Dockerfile` Dateien aufsetzen und damit die Gesamte Applikation mit allen Versionen einstellen. Jetzt müssen nur noch die einzelnen Entwickler Docker auf ihrem PC/Mac haben, den Rest erledigt dann Docker.

## Dockerfile & .yml Speicherorte
Am besten legst du dein `Dockerfile` und `docker-compose.yml` so an, dass es für deine App Sinn macht. Bei mir ist das so, dass das `docker-compose.yml` auf dem Repository-Level ist, und das Dockerfile auf dem Django-Projektlevel. So kann ich meine App direkt starten, wenn ich in die Repository gehe. Außerdem können dann weiter Teile der Applikation am Repository-Level hinzugefügt werden. Wie z.B. andere Webapps, oder Datenbanken, etc.

```
- meine_repository
    - docker-compose.yml
    - mein_django_projekt/
        - Dockerfile
        - manage.py
        - ...
    - venv/
    - .env
    - .gitignore
    - README.md
```

## Dockerfile
Um deine django-App im container zu starten, brauchst du ein Dockerfile. Hier bestimmst du Details deines Applikationsverhaltens im Container. Hier mein Dockerfile für Django mit Erklärungen:

```
# Deine Python-Version gibst du mit Doppelpunkt und der Versionsnummer mit 2 Punkten an.
# So verhinderst du mögliche Probleme mit falschen Versionen
FROM python:3.11.9

# Drei nützliche Optionen für die Python runtime im Container. 
# https://stackoverflow.com/questions/59812009/what-is-the-use-of-pythonunbuffered-in-docker-file
ENV PYTHONUNBUFFERED 1
# https://stackoverflow.com/questions/59732335/is-there-any-disadvantage-in-using-pythondontwritebytecode-in-docker
ENV PYTHONDONTWRITEBYTECODE 1
# https://stackoverflow.com/questions/45594707/what-is-pips-no-cache-dir-good-for
ENV PIP_NO_CACHE_DIR=1

# Du kannst als dein 'Hauptverzeichnis' im Container einfach /app nutzen
# Das 'WORKDIR' wirkt sich auf alle weiteren Docker - Befehle aus, wie z.B.
# den COPY Befehl in der nächsten Zeile. Mit . ist jetzt /app gemeint
WORKDIR /app

# Hiermit kopierst du deine requirements.txt nach /app/requirements.txt
# Dazu muss sich dein requirements.txt in deinem Django-Projektverzeichnis befinden
COPY requirements.txt .

# Jetzt installierst du die requirements.txt
RUN pip install -r requirements.txt

# Du kopierst nun dein gesamtes django-Projekt in den Container
COPY . .

# Gebe deinen Port im Docker-Netzwerk frei
EXPOSE 8000
```

Jetzt kannst du dein Container-Image bauen & laufen lassen:
```
cd mein_projekt
docker build -t mein_container_image .

# Hier sollte nichts passieren
docker run mein_container_image
```

Wieso passiert nichts? Dein Container läuft nur ganz kurz, weil du den `python manage.py runserver` Befehl noch nicht angegeben hast! Das machen wir jetzt.

## docker-compose.yml
Die Orchestrierung deines Containers machen wir aus dem `docker-compose.yml`. Momentan brauchst du nur den einen Service, aber in Zukunft kannst du auch mehrere weitere Services hinzufügen. Folgendermaßen kannst du dein `docker-compose.yml` aufbauen:
```
services:
  meine_app:
    # Automatischer Neustart, falls deine App abstürzt
    restart: always
    # Das Verzeichnis, in dem dein Dockerfile liegt
    build: ./mein_django_projekt
    # Du gibst den Port 8000 frei vom Inneren des Containers zum Äußeren
    ports:
      - "8000:8000"
    # Mit einem Bind-Mount sorgst du dafür, dass der Code deines Projekts
    # Im Container aktualisiert wird, so wie auf deiner Festplatte
    volumes:
      - ./mein_django_projekt/:/app
    # Hier kannst du deine Umgebung angeben:
    env_file: .env
    # Mit diesem Befehl startest du den Server
    # Bei Containern mit 0.0.0.0 starten!
    command: python manage.py runserver 0.0.0.0:8000
```

Jetzt kannst du deine App starten!
Mit `docker compose up` wird nun dein Container gebaut & mit dem runserver-Befehl gestartet. Der Befehl muss im gleichen Verzeichnis wie die `docker-compose.yml` Datei abgesetzt werden.
Nun kannst du unter `localhost:8000` deine App erreichen! 

Happy Coden!
Dein Ruben

[Mein Blog](rubenvoss.de)