====== Dockeritzant una aplicació Django ======
En aquest article dockeritzarem una aplicació Django. Cal haver seguit el [[Django|tutorial inicial Django]] o, com a mínim disposar d'un projecte Django amb el complement django-environ per a les variables d'entorn.
{{ django-docker.png }}
{{tag> #Ciber #CiberMp03 #Ceti #CetiMp03 docker django python }}
Referències:
* [[Django]] : primer de tot cal seguir aquest article per disposar d'una app Django apta per a ser dockeritzada, en particular amb els ajustos per utilitzar les variables d'entorn (django-environ).
* [[Docker]]
* [[https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/|Un bon article de com dockeritzar Django amb Nginx i Gunicorn]].
* [[https://programacionymas.com/blog/docker-diferencia-entrypoint-cmd|Utilitzant ENTRYPOINT i CMD en Dockerfile]]
\\
===== Dockeritzant amb el servidor de desenvolupament =====
El Dockerfile permet crear la imatge del contenidor.
# We Use an official Python runtime as a parent image
FROM python:3.11.7-bullseye
# install db libs
RUN apt-get update
RUN apt-get install -y default-mysql-client libmariadb-dev
RUN apt-get install -y libmariadb-dev-compat gcc gdal-bin libjpeg-dev
# install app libs
COPY requirements.txt requirements.txt
RUN pip3 install --no-cache-dir -r requirements.txt
# Mounts the application code to the image
COPY . code
# establish workdir
WORKDIR /code
EXPOSE 8000
# runs the development server
ENTRYPOINT ["python3","manage.py"]
CMD ["runserver","0.0.0.0:8000"]
Fixeu-vos en què la darrera instrucció equival a ''python3 manage.py runserver 0.0.0.0:8000'', és a dir, que estem fent servir el servidor de desenvolupament, i no un servidor d'aplicacions específic per producció com uWSGI o Gunicorn.
El docker-compose ens facilita la posada en producció juntament amb la base de dades:
version: '3.1'
services:
django_app:
build: .
restart: always
ports:
- 8000:8000
environment:
- DATABASE_URL=sqlite:///db.sqlite3
- DEBUG=False
- SECRET_KEY=asecretkeyblabla
volumes:
- ./db.sqlite3:/code/db.sqlite3
Per posar-ho en marxa primer cal crear un arxiu on persistir la BD local:
$ touch db.sqlite3
$ docker-compose up -d --build
**Troubleshooting**
He tingut alguns problemes amb la [[https://pypi.org/project/mysqlclient/|llibreria mysqlclient]]. He aconseguit que funcionés canviant ''requirements.txt'' a una versió anterior (Juny 2023):
mysqlclient==2.1.1
\\
===== Exercicis =====
Connecta't al contenidor de l'app Django i:
- Fes un //migrate// per tal de crear les taules de la BD.
- Crea un superusuari i loga't al panell d'administració ''/admin''.
Modifica el ''docker-compose.yml'' per tal que s'utilitzi una MySQL enlloc de la SQLite de test.
Assegura't que la BD MySQL sigui persistent.
Fes el //migrate// i crea el superusuari tal i com hem fet abans.
Alguns tips:
* A l'afegir la MySQL la ''django_app'' depèn de la BD per poder funcionar. Per assegurar que primer arrencarà la DB i després la app, cal posar:depends_on:
- db
* Afegiu un arxiu ''.dockerignore'' on li direm quins arxius no cal posar dins el Docker. Si no ho fem, la BD es copiarà dins del Docker de la django_app la 2a vegada que l'executem:
db/
\\
===== Utilitzant uWSGI + Ngnix =====
El "invento" que hem fet fins ara funciona, però **no és una bona pràctica utilitzar el servidor de desenvolupament de Django** (el ''./manage.py runserver''). Ara configurarem el ''Dockerfile'' per aconseguir que s'utilitzi uWSGI + Nginx , tal i com cal per a un servidor de producció.
{{:django:diagrama_django_produccio.png?direct}}
Recordem que un dels principis bàsics de Docker és el de "un servei per contenidor". Com que necessitem 2 serveis, seguint aquesta filosofia necessitarem un ''docker-compose.yml'' amb 2 serveis + la BD. [[https://python.plainenglish.io/dockerizing-django-rest-apis-with-uwsgi-and-nginx-cabffc153c78|Aquest tutorial segueix la filosofia "un servei per contenidor"]].
En canvi, [[https://gabimelo.medium.com/developing-a-flask-api-in-a-docker-container-with-uwsgi-and-nginx-e089e43ed90e|aquest altre tutorial per a Flask integra els 2 processos (uWSGI + Nginx) en un sol contenidor]]. Per tal de tenir diversos serveis utilitza [[http://supervisord.org/|Supervisord]], una utilíssima eina que a mode de //watchdog// vigila que no s'aturi cap dels dos processos.
==== Dockeritzant uWSGI + Nginx en un sol contenidor ====
Farem una mescla dels dos tutorials per aconseguir el nostre Django en un sol contenidor.
[supervisord]
# per a dockeritzar necessitem supervisord en foreground
nodaemon=true
[program:uwsgi]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini --die-on-term
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
[uwsgi]
# TODO: ull, revisa si el teu projecte es diu 'mysite' o una altra cosa
module = mysite.wsgi
callable = application
uid = nginx
gid = nginx
socket = /tmp/uwsgi.sock
chown-socket = www-data:www-data
chmod-socket = 664
cheaper = 1
processes = %(%k + 1)
# clear environment on exit
vacuum=true
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
server {
location / {
try_files $uri @yourapplication;
}
location @yourapplication {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
# TODO: aquí cal afegir alguna cosa perquè funcionin el arxius estàtics
}
# We Use an official Python runtime as a parent image
FROM python:3.11.7-bullseye
# install db libs
RUN apt-get update
RUN apt-get install -y default-mysql-client libmariadb-dev
RUN apt-get install -y libmariadb-dev-compat gcc gdal-bin libjpeg-dev
# install nginx and supervisor
RUN apt-get install -y --no-install-recommends \
nginx supervisor
RUN useradd --no-create-home nginx
# install uwsgi
RUN pip3 install uwsgi
# adjustments
RUN rm /etc/nginx/sites-enabled/default
RUN rm -r /root/.cache
# production conf files
COPY deploy/django-site.conf /etc/nginx/conf.d/
COPY deploy/uwsgi.ini /etc/uwsgi/
COPY deploy/supervisord.conf /etc/
# install app libs
COPY requirements.txt requirements.txt
RUN pip3 install --no-cache-dir -r requirements.txt
# Mounts the application code to the image
COPY . code
# establish workdir
WORKDIR /code
EXPOSE 80
# runs the development server
CMD ["/usr/bin/supervisord"]
Perquè funcioni bé i veiem la web al port 8000 cal que **modifiqueu el ''docker-compose.yml'' perquè connecti el port 8000 de la màquina host amb el port 80 del contenidor del Nginx**.
\\
Canvia la configuració per a utilitzar el servidor d'aplicacions uWSGI + Nginx enlloc del servidor de desenvolupament.
Ull que alguns arxius van a una nova carpeta ''/deploy''.
Comprova que **tornem a tenir el problema dels arxius estàtics** accedint al /admin i refrescant fins que vegis que no es veuen els CSS.
Com creus que seria la millor manera de solventar el problema dels arxius estàtics? Implementa-la.