Docker e Django: Come Creare un'Applicazione Web con Docker e PostgreSQL
Qualche giorno fa, attraverso un sondaggio sui social, è venuto fuori che Docker e Python visti tramite un approccio pratico sono due tematiche che stanno particolarmente a cuore. E allora perché non approfittarne?
Partiamo con Docker in pratica: come rendere un’applicazione scritta in Django, uno dei più noti framework di Python per creare delle interfacce web, attraverso l’uso di container.
E perché non farlo usando il sito web delle Django Girls Italia?
_Via alla volta di Docker per Django e Postgres!
Requisiti
- Python 3.x installato
- Django installato
- Docker installato
Step 1: progetto Django
In questo caso, andremo ad usare un caso d’uso molto semplice, fornito dal mitico gruppo delle Django Girls Italia (qui il link).
Vediamo insieme i file principali dell’applicazione:
- La directory principale farà da contenitore per il progetto, ed è quella esterna di djangogirls;
- manage.py: si tratta di un file che consente di interagire con questo progetto Django in vari modi e di agire come se stessimo utilizzando django-admin;
- __init__.py: file vuoto che dice a Python che questa directory dovrebbe essere considerata un pacchetto;
- settings.py: file per la configurazione del progetto;
- urls.py: in questo file, andremo a specificare le URL che andremo ad esporre; si tratta di una sorta di “indice” del sito basato su Django.
Eseguendo il seguente comando, dovremmo ottenere nel terminale un risultato simile a quello che segue:
> python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.
November 23, 2021 - 15:50:53
Django version 3.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
In questo modo, hai avviato il server di sviluppo Django, estremamente leggero e scritto esclusivamente in Python (ovviamente!).
Questo perché non vale la pena preoccuparsi in questa dase di utilizzare un server più potente o complesso, ma piuttosto demandiamo questo step a quando saremo pronti per la messa in produzione!
Step 2: definizione del Dockerfile
Ricordiamo: un Dockerfile è un documento di testo che contiene tutti i comandi che un utente potrebbe chiamare sulla riga di comando per assemblare un’immagine; una sorta di ricetta per preparare la nostra applicazione.
In questo caso, lo andremo a creare all’interno della cartella principale del progetto, come mostrato nella figura seguente:
Struttura del progetto DjangoGirls con Docker per Django e Postgres
Il Dockerfile conterrà quindi tutte le istruzioni per far sì che l’applicazione possa essere avviata tramite un container:
FROM python:3
ENV PYTHONUNBUFFERED 1
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt && pip install --upgrade pip
COPY . .
RUN chmod a+x run.sh
CMD ["./run.sh"]
Come prima cosa, andiamo a definire l’immagine di base: in questo caso, tramite l’istruzione FROM, specifichiamo che l’immagine da utilizzare è quella che contiene Python in versione 3.x, basata normalmente sul sistema operativo Alpine (un sistema molto minimal derivato da Ubuntu).
La seconda riga imposta una variabile d’ambiente che assicura che l’output di Python venga inviato direttamente al terminale senza prima memorizzarlo nel buffer; questo garantisce che nessun output venga conservato da qualche parte e mai scritto nel caso in cui l’applicazione Python si arresti in modo anomalo.
Si tratta di una configurazione piuttosto comune quando si ha a che fare con applicazioni Django!
La terza riga permette di configurare la cartella di lavoro all’interno del container su /app: questo vuol dire che il progetto Django verrà copiato e lavorato al suo interno, come mostrato nella riga successiva.
La riga n. 4 che contiene l’istruzione COPY copia il file requirements.txt che contiene i moduli e le relative versioni delle dipendenze di Python da installare: in questo caso, c’è solo Django e molto altro, ma è possibile aggiungervi tutti i moduli necessari.
L’istruzione RUN permette di installare i requisiti specificati nel file appena copiato e procediamo anche con l’aggiornamento di pip: non è necessario, ma tuttavia è consigliato farlo!
Successivamente, copiamo il file chiamato run.sh: grazie a questo file, possiamo aggiungere tutte le operazioni preliminari che ci permetteranno di rendere funzionante il sito in un ambiente di sviluppo (maggiori informazioni sono contenute nel README.md del progetto!).
Le ultime due righe ci permettono di aggiornare i permessi dello script in modo che sia eseguibile (a sta per modificare permesso di esecuzione a tutti gli utenti e gruppi) e poi procediamo con l’esecuzione dello script.
Questo però non basta: il nostro progetto ha bisogno anche di un Postgres. E perché non sfruttare Docker Compose per tirare su entrambi i servizi?
Step 3: configurazione docker-compose.yml
Al suo interno andremo a definire tutti i requisiti necessari per far sì che in prima battuta venga avviato il database, e poi venga tirato su il server per l’applicazione Django.
Il file docker-compose.yml avrà quindi questo aspetto:
version: "3.9"
services:
db:
image: postgres
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: djangogirls
POSTGRES_PASSWORD: postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- POSTGRES_NAME=postgres
- POSTGRES_USER=djangogirls
- POSTGRES_PASS=postgres
depends_on:
- db
Definisco due servizi, uno per il database (chiamato db) e uno per la parte web (chiamato proprio web).
Nella sezione relativa al database, vado a specificare come immagine di base l’ultima di Postgres disponibile nei registry di DockerHub e aggiungo anche le variabili di ambiente che mi permetteranno di creare il database.
Come indicato infatti all’interno del README.md del progetto, sarà necessario creare un database chiamato djangogirls con l’utenza postgres/postgres: andiamo quindi ad indicare questi valori all’interno della proprietà environment.
...
db:
image: postgres
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: djangogirls
POSTGRES_PASSWORD: postgres
...
Per quanto riguarda l’applicazione Django, abbiamo a disposizione un Dockerfile che ne farà la build come abbiamo visto allo step 2: sarà quindi sufficiente indicare la cartella attuale come istruzione per la build.
...
web:
build: .
...
Per far sì che la nostra applicazione, una volta modificata, sia persistente, utilizziamo la direttiva volumes in entrambi i servizi: nel primo caso, andrà a salvare le informazioni del database, mentre nel secondo caso userà il codice sorgente specificato sotto /app.
...
db:
image: postgres
volumes:
- ./data/db:/var/lib/postgresql/data
...
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
...
Nella sezione ports indichiamo la porta 8000 come quella su cui il container dev’essere in ascolto e infine specifichiamo il comando di avvio nella sezione command, così come riportato nel README.md: questo avvierà il server di Django.
Questo ci permette di avviare il server che conterrà l’applicazione Django: normalmente, si usa django-admin.
django-admin è infatti lo strumento che offre Django per le attività amministrative; il file manage.py viene creato automaticamente in ogni progetto Django e fa la stessa cosa di django-admin, ma imposta anche la variabile d’ambiente DJANGO_SETTINGS_MODULE in modo che punti al file settings.py del tuo progetto.
Generalmente, quando si lavora su un singolo progetto Django, è più facile usare manage.py rispetto a django-admin. Se devi passare da un file di impostazioni Django a un altro, usa django-admin con DJANGO_SETTINGS_MODULE o l’opzione della riga di comando --settings.
Ultimo, ma non ultimo, la clausola depends_on: questa fa sì che il servizio di Django non si avvii fintanto che il database non è in esecuzione. Piccola nota: questo non lo salverà da eventuali errori: se venisse avviato con dei problemi, la dipendenza sarà comunque soddisfatta!
...
depends_on:
- db
...
Step 4: run!
E dunque, via col mambo: digitiamo il seguente comando da terminale (che accorcia in un unico comando sia la fase di build che di run dei servizi) e apriamo il browser all’indirizzo localhost:8000 per poter vedere la homepage!
$ docker-compose up --build
Pagina principale della community DjangoGirls
Ce l’hai fatta! Docker per Django e Postgres realizzato in un baleno :)
Disclaimer
Questo caso d’uso è perfetto per un ambiente di sviluppo, ma non per la messa in produzione. In quel caso, è fondamentale utilizzare un web server diverso da quello di default, come Nginx.
Per farlo, è sufficiente (a grandi linee) modificare il comando di esecuzione nel docker-compose.yml per avviare l’applicazione sfruttando un webserver diverso e aggiungere ai servizi Nginx o qualsiasi web server che sia adatto al compito!
Risorse utili
- Docker - per cominciare bene con Docker e Kubernetes
- Kubernetes - Guida per gestire e orchestrare i container
- Podman e Docker Compose
- Esplorando il Dockerfile
- MEAN Docker: sviluppare una webapp da zero in meno di un’ora
- Docker in pillole