Django im Devcontainer mit PostgreSQL-Datenbank und Redis
Die Funktionsweise von Devcontainern habe ich bereits kurz im Artikel Typst im Devcontainer beschrieben. Während in diesem Beispiel die Entwicklungsumgebung aus nur einem Container bestand, zeige ich hier, wie sich komplexere Aufbauten realisieren lassen.
Was werden wir erreichen?
Wir erhalten eine containerisierte Entwicklungsumgebung für ein Django-Projekt, bestehend aus dem eigentlichen Entwicklungscontainer, einer PostgreSQL-Datenbank und einer Redis-Instanz. Dazu bekommen wir geeignete browserbasierte Werkzeuge für Datenbank und Key-Value-Store.
Die Entwicklungsumgebung wird so konstruiert sein, dass sie sowohl als Development Container, als auch in Github Codespaces und DevPod funktionieren wird.
So zeige ich, wie eine schon etwas komplexere Umgebung konstruiert werden kann.
Dieser Projektaufbau hat es schon in sich. Daher stelle ich das komplette Grundgerüst auf git.codebau.dev zur Verfügung.
Was brauchen wir noch gleich?
Als Beispiel dient eine Django-Anwendung, die eine PostgreSQL-Datenbank und Redis als Cache bzw. Queue-System, z. B. für Celery, braucht. Alles in einem Container ist unpraktisch und benötigt einigen Aufwand bei der Erstellung und Konfiguration. Eine der Stärken von Containern liegt darin, dass ein Container konkret eine Aufgabe hat. So brauchen wir also mindestens diese drei verschiedenen Container:
- Ein Container mit Redis für Cache1
- Ein Container mit PostgreSQL2
- Ein Container mit Python zur Django-Entwicklung3
Der zuletzt genannte Container ist der Hauptcontainer für die Anwendung. Alle Container müssen untereinander kommunizieren können. Um das Entwickler-Erlebnis zu verbessern, fügen wir noch zwei weitere Container hinzu:
- Ein Container mit
pgweb
zum browserbasierten Zugriff auf die PostgreSQL-Datenbank - Ein Container mit
redis-commander
zum browserbasierten Zugriff auf Redis
Außerdem wollen wir auf verschiedene Ports zugreifen können: Neben der eigentlichen Django-Anwendung natürlich auf die beiden browserbasierten Werkzeuge, sowie auf die PostgreSQL-Datenbank und die Redis-Instanz direkt.
Es handelt sich hier um eine Entwicklungsumgebung. Außerhalb des Lebenszyklus der Container ist keine Persistenz von Daten in Redis oder der PostgreSQL-DB vorgesehen.
Die Grenzen
Es handelt sich um eine Entwicklungsumgebung zur lokalen Verwendung. Benutzernamen und Passwörter sind schwach. Das, was wir hier bauen, darf niemals ein Deployment für eine produktive Umgebung darstellen.
Nochmal: Es ist und bleibt eine Entwicklungsumgebung.
Der Grundaufbau
Natürlich folgt auch dieses Projekt den Konventionen für einen Development Container. Entsprechend sieht die Grundstruktur ähnlich der Struktur aus, die wir schon aus dem Typst-Artikel kennen.
1DjangoApp/
2├─ .devcontainer/
3│ ├─ devcontainer.json
4│ ├─ ... weitere Dev Container Files ...
5├─ .gitignore
6├─ ... Django Project Files ...
7├─ README.md
Da nun mehr als ein Container verwendet wird, nutzen wir die Mittel von
Docker Compose. Entsprechend ist die devcontainer.json
nun etwas anders
aufgebaut:
1{
2 "name": "DjangoApp",
3 "dockerComposeFile": "docker-compose.yml",
4 "service": "devcontainer",
5 "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
6 "remoteUser": "root",
7 "mounts": [],
8 "customizations": {
9 "vscode": {
10 "extensions": [
11 "ms-python.python",
12 "ms-python.debugpy",
13 "ms-azuretools.vscode-docker"
14 ]
15 }
16 },
17 "forwardPorts": [
18 8000,
19 "db:5432",
20 "cache:6379",
21 "pgweb:8081",
22 "commander:8080"
23 ],
24 "portsAttributes": {
25 "8000": {
26 "label": "Django Application"
27 },
28 "db:5432": {
29 "label": "PostgreSQL DB Instance"
30 },
31 "cache:6379": {
32 "label": "Redis Cache Instance"
33 },
34 "pgweb:8081": {
35 "label": "pgWeb"
36 },
37 "commander:8080": {
38 "label": "Redis Commander"
39 }
40 }
41}
Neben dem Projektnamen in Zeile 2 wird nun als erstes auf die docker-compose.yml
(Zeile 3) verwiesen.
Mit dem exakten Inhalt dieser Datei befassen wir uns gleich. Zunächst ist hier noch Zeile 4 wichtig. Sie gibt
an, welcher der Container aus der docker-compose.yml
nun der Container ist, der für die eigentliche Software-
Entwicklung verwendet wird.
Die Angabe unter workspaceFolder
in Zeile 5 gibt, wie im Typst-Beispiel, an, an welcher Stelle im Container
wir unseren »Code« bearbeiten wollen. Ich verwende hier wieder die Standard-Angabe.
Die Zeilen 8-16 geben an, welche Extensions wir nutzen wollen, wenn Visual Studio Code als IDE zum Einsatz
kommen soll. Für die meisten Python-Projekte genügen mir persönlich die ersten beiden genannten Erweiterungen.
Mit ihnen kann ich Python-Code editieren, ausführen und debuggen. Die dritte Erweiterung unterstützt bei der
Erstellung von Docker-Dateien (Dockerfile
, docker-compose.yml
).
Der nächste Teil der devcontainer.json
beschreibt die aus den Containern weitergeleiteten Ports. Die
Zeilen 17-23 benennen diese Ports zunächst. Hier fällt auf, dass ein Port wie gewohnt als einfache Nummer
angegeben ist. Es ist das Port, unter dem die Django-Applikation im Browser erreichbar sein wird. Bei
Django-Projekten ist das standardmäßig das Port 8000
. Die anderen Ports sind als String mit einem Namen
davor angegeben. Diese Namen begegnen uns im nächsten Abschnitt in der docker-compose.yml
wieder. Im Ergebnis
bedeutet das, dass diese Ports aus anderen Containern als dem eigentlichen Entwicklungscontainer zum
Entwicklerrechner weitergeleitet werden.
Zur besseren Übersicht geben die Zeilen 24-41 noch jedem weitergeleiteten Port ein Label. So kann man in der »Ports-Übersicht« in Visual Studio Code nachher nachvollziehen, welches Port welchen Zweck hat.
Ins Detail: docker-compose.yml
Im Verzeichnis .devcontainer
, direkt neben der Datei devcontainer.json
, liegt die Datei docker-compose.yml
.
Sie hat den folgenden Inhalt:
1version: '3.8'
2
3services:
4 devcontainer:
5 build:
6 context: .
7 dockerfile: Dockerfile
8 command: sleep infinity
9 volumes:
10 - ../..:/workspaces:cached
11 db:
12 image: postgres:alpine
13 restart: always
14 environment:
15 POSTGRES_PASSWORD: developer
16 POSTGRES_USER: developer
17 cache:
18 image: redis:alpine
19 restart: always
20 pgweb:
21 image: ghcr.io/sosedoff/pgweb:latest
22 restart: always
23 environment:
24 DATABASE_URL: postgres://developer:developer@db:5432/developer?sslmode=disable
25 depends_on:
26 - db
27 commander:
28 image: ghcr.io/joeferner/redis-commander:latest
29 restart: always
30 environment:
31 REDIS_HOSTS: Django-Cache:cache:6379:10
32 PORT: 8080
33 depends_on:
34 - cache
35 user: redis
Hier finden wir alle Container wieder, deren Namen wir zuvor bereits gesehen haben. Doch der Reihe nach:
Die Zeilen 4-10 beschreiben den eigentlichen Entwicklungscontainer für das Django-Projekt. Da wir hier
noch Anpassungen am Standard-Image vornehmen wollen, verweisen wir auf einen Dockerfile
, um den wir uns
im nächsten Abschnitt kümmern.
Die Zeilen 11-16 definieren den Container für die PostgreSQL-Datenbank, basierend auf dem offiziellen Docker-Image2. Als Umgebungsvariablen werden ein Benutzername und ein Kennwort definiert. Beides brauchen wir für die Konfiguration im Django-Projekt, aber auch für den »pgweb«-Container, um den es gleich noch gehen wird.
Die Zeilen 17-19 definieren den Container für das Redis Key-Value-Store. Es wird auch hierfür das offizielle Docker-Image1 genutzt. In der Standard-Konfiguration benötigt Redis keine Zugangsdaten, daher müssen keine weiteren Informationen angegeben werden.
Die Zeilen 20-26 bringen pgweb
als Tool für den browserbasierten Zugriff auf die PostgreSQL-Datenbank
ins Spiel. Durch die Angabe der Verbindungs-URL als Umgebungsvariable lässt sich das Tool komplett
vorkonfigurieren (Zeile 24). Hier finden sich die Zugangsdaten zur PostgreSQL-Datenbank.
In den Zeilen 27-35 wird schlussendlich das Tool für den browserbasierten Zugriff auf Redis bereitgestellt. Auch hier findet die Konfiguration über die Umgebungsvariablen statt.
Eine Ebene tiefer: Dockerfile
Für den eigentlichen Entwicklungscontainer brauchen wir das Standard-Python-Docker-Image3 und
passen es entsprechend an. Das Dockerfile
liegt ebenfalls im Verzeichnis .devcontainer
, neben den Dateien
devcontainer.json
und docker-compose.yml
. Der Inhalt:
1FROM python:3-alpine
2
3RUN apk update && apk upgrade
4RUN apk add git gpg wget curl bash bash-completion shadow nano starship openssh-client openssh-sftp-server sqlite sqlite-dev
5
6RUN chsh -s /bin/bash
7RUN echo 'eval "$(starship init bash)"' >> ~/.bashrc
8
9RUN python3 -m pip install -U pip
10RUN python3 -m pip install -U pipx
11RUN python3 -m pipx ensurepath
12RUN pipx install poetry
Das kompakte Alpine-basierte Image ist eine gute Basis für Django-Projekte (Zeile 1). Die Zeilen 3-4
installieren eine Reihe an benötigten Standard-Paketen. Zeilen 6-7 ändern die Standard-Shell auf bash
und aktivieren starship
4. Beides kennen wir aus dem Typst-Projekt schon. Für mich erhöht starship
einfach den Komfort auf dem Terminal des Entwicklungscontainers.
Ich bevorzuge für meine Python-Projektverwaltung das Tool poetry
5. Dies muss zunächst installiert
werden. Dazu wird in den Zeilen 9-12 pip
aktualisiert, pipx
installiert, pipx
angewiesen, den PATH
anzupassen (sofern erforderlich), und letztlich poetry
zu installieren.
Fertig ist der Container.
Wie geht es weiter?
Nun kann man das Projekt z. B. mit Visual Studio Code nun »im Devcontainer« öffnen. Ist dies gelungen, kann
man im VS Code Terminal das Projekt mit poetry init
starten. Auf die weitere Projektkonfiguration gehe ich
im Rahmen dieses Posts nicht weiter ein. Damit aber das frisch angelegte Django-Projekt die PostgreSQL-Datenbank
nutzt, muss das Python-Package psycopg
6 installiert werden. Dann kann man die DB-Konfiguration in
der settings.py
so anpassen:
1DATABASES = {
2 "default": {
3 "ENGINE": "django.db.backends.postgresql",
4 "OPTIONS": {
5 "pool": True,
6 },
7 "NAME": "developer",
8 "USER": "developer",
9 "PASSWORD": "developer",
10 "HOST": "db",
11 "PORT": 5432,
12 },
13}
Außerdem soll Redis genutzt werden. Entsprechend wird die settings.py
um die Konfiguration für das Cache
und das Session-Handling erweitert, nachdem das Python-Package django-redis
installiert wurde:
1CACHES = {
2 'default': {
3 'BACKEND': 'django_redis.cache.RedisCache',
4 'LOCATION': 'redis://cache:6379/10',
5 'OPTIONS': {
6 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
7 }
8 }
9}
10
11SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
12SESSION_CACHE_ALIAS = 'default'
Im Beispiel hier wird die DB Nr. 10 der Redis-Datenbank in dem Container »cache« verwendet.
Jetzt ist alles bereit für die Entwicklung. Viel Spaß!
Obwohl hier mehrere Container verwendet werden: Die nun erstellte Konfiguration kann auch mit GitHub-Codespaces verwendet werden.
-
Wir werden das offizielle Redis-Docker-Image verwenden. ↩︎ ↩︎
-
Wir werden das offizielle PostgreSQL-Docker-Inage verwenden. ↩︎ ↩︎
-
Wir werden das offizielle Python-Docker-Image für unsere Zwecke anpassen und etwas erweitern. ↩︎ ↩︎
-
Siehe starship.rs. ↩︎
-
Siehe python-poetry.org. ↩︎
-
Bitte nicht mit
psycopg2
verwechseln –psycopg
ist der Nachfolger vonpsycopg2
. Siehe dazu psycopg.org. ↩︎
Entwicklung Devcontainer Django
Zuletzt geändert: 2024-10-29 21:34:11