Как мы разворачивали React + Django приложение: пошаговое руководство

Серии -

В предыдущей статье мы настраивали окружение разработчика для создания приложений с использованием React и Django. В этой статье мы поделимся нашим опытом развертывания Django приложения с помощью Docker контейнеров, описывая ключевые шаги и лучшие практики, чтобы получить продакшн окружение.

В последние годы использование асинхронных приложений на базе Django (ASGI) стало популярным благодаря их высокой производительности и возможности обработки большого количества соединений. Мы будем разворачивать Django ASGI приложение с использованием Nginx и SSL сертификатом от Let’s Encrypt.

Развернем приложение из предыдущей статьи. Настроим конфигурацию Nginx для вашего приложения. Создайте файл your_domain.conf в каталоге frontend/nginx:

nginx

server {
    listen      80;
    listen [::]:80;
    gzip on;
    gzip_types text/plain text/css text/js text/xml text/javascript application/javascript application/json
        application/xml application/rss+xml image/svg+xml;

    server_name your_domain.com;

    # Указываем расположение вашего React проекта в этом контейнере. Оно задается в Dockerfile
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # Проксируем все обращения к API сервера в другой контейнер с Django
    # Имя контейнера backend задается в docker-compose.yml файле
    location /api/ {
        proxy_pass http://backend:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # Этот каталог нужен для получения сертификата от Let's Encrypt
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/cert;
    }
}

Для развертывания React приложения можно собрать ваш проект командой npm run build. В результате этого получим каталог build, который можно подключить к Nginx контейнеру. Либо сделать новый образ Nginx и включить React приложение в него. Для этого в каталоге с frontend приложением создаем следующий Dockerfile файл:

dockerfile

# Этап сборки
FROM node:current AS build

# Устанавливаем рабочую директорию
WORKDIR /app

# Копируем все файлы приложения
COPY . .

# Устанавливаем зависимости
RUN npm install

# Создаем сборку приложения
RUN npm run build


# Этап развертывания
FROM nginx

# Копируем конфигурацию nginx
COPY ./nginx/* /etc/nginx/conf.d

# Копируем файлы сборки из предыдущего этапа
# Если используете сборщик пакетов Vite, то он создает прилежение в каталоге dist
#COPY --from=build /app/dist /usr/share/nginx/html
COPY --from=build /app/build /usr/share/nginx/html

В качестве сервера приложений будем использовать Daphne. Его нет requirements.txt, поэтому ставим отдельно. В корне проекта backend сервера создаем Dockerfile следующего содержания:

dockerfile

# Используем последний официальный образ Python в качестве базового
FROM python:3

# Устанавливаем рабочую директорию
WORKDIR /app

# Копируем все файлы проекта в контейнер
COPY . .

# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt daphne

# Выполняем миграции и собираем статические файлы
RUN python manage.py migrate
RUN python manage.py collectstatic --noinput

# Указываем команду для запуска приложения
CMD ["daphne", "-b", "0.0.0.0", "-p", "8000", "backend.asgi:application"]

В корне нашего проекта создаем файл docker-compose.yml со следующим содержанием:

yaml

services:
  web:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend

  backend:
    build: ./backend

На этом этапе уже можно запустить наши контейнеры и проверить их работу.

shell

docker compose up -d

Если сейчас перейти по вашему доменному имени, должно открыться наше приложение.

Для этого запустим отдельный контейнер, который периодически будет проверять сертификаты и запрашивать новые. Для этого у него должно быть общее хранилище вместе с контейнером nginx. Точнее таких хранилищ должно быть два: для хранения сертификатов и для прохождения проверки для выдачи сертификата. Приведем наш docker-compose.yml файл к следующему виду:

yaml

services:
  web:
    build: ./frontend
    volumes:
       - certbot-etc:/etc/letsencrypt
       - certbot-var:/var/cert
    ports:
      - "80:80"
      - "443:443"
    command: "/bin/sh -c 'while :; do sleep 12h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  backend:
    build: ./backend
    volumes:
      - .:/app

  certbot:
     image: certbot/certbot
     volumes:
       - certbot-etc:/etc/letsencrypt
       - certbot-var:/var/cert
     depends_on:
       - web
     entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  certbot-etc:
  certbot-var:

Перезапускаем контейнеры и пробуем получить новый сертификат. Не забываем подставить свое доменное имя и почтовый ящик. Также, для тестирования, запускайте с опций --staging, т.е. у Let’s Encrypt очень маленькие лимиты на попытки и в случае неудач, придется по часу ожидать. А поле того как все начало работать без ошибок, просто уберите эту опцию или поменяйте на --force чтобы обновить еще не истекший сертификат.

shell

docker compose up -d
docker compose run --entrypoint "certbot certonly --webroot --webroot-path=/var/cert \
    --email you@email --agree-tos --no-eff-email --staging -d your_domain.com \
    -d www.your_domain.com" certbot

Теперь нам нужно перенастроить Nginx на работу с полученным сертификатом.
Приведем файл your_domain.conf в каталоге frontend/nginx к такому виду:

nginx

# Делаем переадресацию на https
server {
    listen      80;
    listen [::]:80;
    access_log off;

    server_name your_domain.com www.your_domain.com;

    return 301 https://your_domain.com$request_uri;
}

# Делаем переадресацию с поддомена www на без него. Либо наоборот, в зависимости от ваших предпочтений.
server {
    listen      443 ssl;
    listen [::]:443 ssl;
    access_log off;

    server_name www.your_domain.com;
    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
    include conf.d/common_params;

    return 301 https://your_domain.com$request_uri;
}

# Основной блок обработки запросов
server {
    listen      443 ssl;
    listen [::]:443 ssl;
    http2 on;

    server_name your_domain.com;

    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
    include conf.d/common_params;

    # Указываем расположение вашего React проекта в этом контейнере. Оно задается в Dockerfile
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # Проксируем все обращения к API сервера в другой контейнер с Django
    # Имя контейнера backend задается в docker-compose.yml файле
    location /api/ {
        proxy_pass http://backend:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # Этот каталог нужен для получения сертификата от Let's Encrypt
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/cert;
    }
}

Так же добавим в этот каталог файл common_params с общими параметрами для большинства сайтов

nginx

gzip on;
gzip_types text/plain text/css text/js text/xml text/javascript application/javascript application/json
    application/xml application/rss+xml image/svg+xml;
server_tokens off;

ssl_buffer_size 4k;
ssl_session_cache shared:TLS:2m;

# Сгенерировать случайный dhparam файл можно с помощью следующей команды:
# openssl dhparam 4096 -out /etc/nginx/conf.d/dhparam.pem
ssl_dhparam /etc/nginx/conf.d/dhparam.pem;

ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers on;

ssl_ecdh_curve secp521r1:secp384r1;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]; # Cloudflare

# Set HSTS to 365 days
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;

Пересобираем наш контейнер docker compose build web и запускам docker compose up -d. Если все сделано правильно, то мы увидим работающий сервер через https соединение с автоматическим обновлением сертификатов.

Похожее