Taula de continguts

Dockeritzant una aplicació Django

En aquest article dockeritzarem una aplicació Django. Cal haver seguit el tutorial inicial Django o, com a mínim disposar d'un projecte Django amb el complement django-environ per a les variables d'entorn.

, , , , , ,

Referències:


Dockeritzant amb el servidor de desenvolupament

El Dockerfile permet crear la imatge del contenidor.

Dockerfile
# 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:

docker-compose.yml
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 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:

  1. Fes un migrate per tal de crear les taules de la BD.
  2. 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:
    .dockerignore
    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ó.

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. Aquest tutorial segueix la filosofia "un servei per contenidor".

En canvi, aquest altre tutorial per a Flask integra els 2 processos (uWSGI + Nginx) en un sol contenidor. Per tal de tenir diversos serveis utilitza 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.

deploy/supervisord.conf
[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;'
deploy/uwsgi.ini
[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
deploy/uwsgi_params
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;
deploy/django-site.conf
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
}
Dockerfile
# 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.