В данной статье рассмотрим пример создания Docker-файла для проекта на Django.
Базовый образ и зависимости
Начнём с выбора образа. Если нет ограничений по объёму жёсткого диска, я предпочитаю использовать образ slim-buster. Если ограничения есть, лучше взять alpine, но в этом случае, возможно, потребуется установка дополнительных пакетов в процессе сборки контейнера. Итак, выбираем нужную версию Python и добавляем образ в Dockerfile:
FROM python:3.10.7-slim-busterСоздаём рабочий каталог для приложения и делаем его активным:
WORKDIR /appЗатем приступаем к установке Python-библиотек и зависимостей. Я использую для этих целей pip, а зависимости храню в файле requirements.txt. Копируем файл в контейнер и устанавливаем зависимости:
COPY ./requirements.txt requirements.txt
RUN pip3 install -r requirements.txt --no-cache-dir --no-warn-script-locationЗатем копируем остальные файлы проекта:
COPY . .Раздача статики через nginx
Раздача статических файлов. Если статические и медиафайлы хранятся вместе с Django, необходимо настроить их раздачу. При запуске development-сервера Django командой python manage.py runserver раздача осуществляется автоматически, но в продакшене он обычно не используется. Рассмотрим вариант с раздачей статики через nginx.
Добавим nginx в контейнер, произведём настройку и соберём статику:
RUN apt-get update
RUN apt-get -y install nginx
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/site.conf /etc/nginx/conf.d/site.conf
RUN python manage.py collectstatic --noinputНастройки nginx в файле site.conf следующие:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /data;
location @backend {
proxy_pass http://localhost:8000;
}
location / {
try_files $uri $uri/ @backend;
}
}Настройки в файле nginx.conf следующие:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
tcp_nodelay on;
tcp_nopush on;
client_max_body_size 200m;
gzip on;
gzip_comp_level 3;
gzip_min_length 1000;
gzip_types
text/plain
text/css
application/json
application/x-javascript
text/xml
text/javascript;
proxy_redirect off;
proxy_set_header Host $host;
include conf.d/*.conf;
server_tokens off;
}Здесь мы указываем, что все запросы на 80-й порт проксируются на http://localhost:8000. Также проверяется наличие файлов по URL http://localhost:8000. Если файл есть, он отдаётся nginx'ом без дальнейшего запроса к бэкенду.
Данную операцию производить необязательно, если файлы хранятся в S3-хранилище.
Одна из общих рекомендаций при использовании Docker-контейнеров — запускать команду контейнера под пользователем, отличным от root. Но поскольку нам нужно запустить nginx под root, придётся оставить root.
Порт и скрипт запуска
Укажем контейнеру, какой порт должен быть открыт наружу, и добавим скрипт запуска.
EXPOSE 80
CMD ["./entrypoint.sh"]Запуск производим отдельным скриптом, чтобы запустить nginx, выполнить миграции, а также запустить Django-сервер под gunicorn. Состав скрипта следующий:
#!/bin/bash
echo "Starting service..."
/usr/sbin/nginx
python manage.py migrate
gunicorn --bind=0.0.0.0:8000 pandora_research_backend.wsgiСборка и запуск
На этом всё. Пробуем собрать образ командой:
docker build -t django-img-demo .И затем стартуем его:
docker run -p 80:80 --name django-demo django-img-demoПосмотреть результат можно, введя в браузере путь http://localhost:80 или http://localhost.
Недостатки и решения
В результате мы получили контейнер, в котором запущен веб-сервер на Django, и доступ к нему можно получить по стандартному 80-му порту. Несмотря на простоту, этот вариант имеет ряд недостатков, из-за которых в продакшене его всё же лучше не использовать. Недостатки и возможные решения перечислены ниже.
Запуск контейнера под root. Для решения этой проблемы можно развести Django-сервер и nginx по разным контейнерам, а затем соркестрировать их через docker-compose. Для обмена статикой можно примонтировать общий каталог на сервере к каждому из контейнеров и использовать его для передачи статики между Django и nginx.
Можно подключить S3-хранилище и использовать его для раздачи статики. Существует немало библиотек для Django, которые позволяют это сделать. В этом случае потребность в раздаче статики отпадёт, и веб-сервер Django уже можно будет запустить под специально созданным пользователем. Код для добавления пользователя следующий:
RUN groupadd -r api_group
RUN useradd -d /app -r -g api_group api_user
RUN chown api_user:api_group -R /app
USER api_user