В этой статье будет рассмотрена установка фреймворка Django, вместе с базой данных PostgreSQL, веб сервером Gunicorn и Nginx на контейнерах Docker с Compose. У вас уже должен быть установлен Docker и Compose. Так же, желательно, иметь хотя бы поверхностное представление о перечисленных технологиях. Так как в подобной настройке легко ошибиться - рекомендуется повторять все в точности.
В рамках статьи мы рассматриваем создание образа для локальной разработки и, с существующими наработками, создание образа для продакшена. На готовые образ, со всеми файлами и комментариями, можно посмотреть на GitHub.
Я буду использовать python3.8 для создания проекта на django 3+, но все операции (с оговорками в определенных местах) подойдут и для python 3.6+ и django 2+.
Создание проекта Django для тестирования
Для создания всех файлов проекта я создам директорию 'project'. Расположение этого каталога не имеет значения:
mkdir project
cd project
Что бы мы могли проверять работу приложений друг с другом - создадим тестовый проект Django. Вместе тестового проекта вы можете использовать ваш существующий, но тогда следите за наименованиями. Название каталога для Django будет 'app':
mkdir app
cd app
Во время создания проектов Python (в т.ч. Django) используется модель виртуального окружения. Виртуальное окружение помогает изолировать версии установленных пакетов и легко их переносить на другие компьютеры и проекты. Вообще этот модуль нужен только для конкретного тестового проекта и не будет переноситься в контейнер.
В Ubuntu, например, он устанавливается отдельно и называется 'python3-venv':
sudo apt install python3-venv
Следующей командой мы создадим окружение и активируем его:
python3 -m venv env
source env/bin/activate
О том что виртуально окружение активировалось говорит строка '(env)' в начале командной строки. Если у вас остались вопросы, то почитайте статью по созданию виртуального окружения на Python.
Теперь установим Django через менеджер пакетов pip:
pip3 install django
Находясь в папке 'app' создадим проект с названием 'django_project' используя следующую команду:
django-admin.py startproject django_project .
Миграции Django - это процесс создания таблиц в базе данных. По умолчанию используется база SQLite. Учитывая, что мы будем использовать PostgreSQL вы можете их не проводить, но это способ проверки, что Django установлен и работает:
python manage.py migrate
Следующей командой мы запустим сервер Django:
python manage.py runserver
О том что Django работает говорить сообщение в скриншоте выше. Вы так же можете выполнить запрос или открыть страницу по адресу 'http://127.0.0.1:8000' через браузер или выполнить команду:
curl http://127.0.0.1:8000
Для того что бы мы могли переносить версии пакетов созданные в виртуальном окружении создадим файл 'requirements.txt' следующим образом:
pip3 freeze > requirements.txt
Так же рекомендую сравнить структуру папок, которая должна быть у вас на этом моменте.
Установка и настройка Django 3 с Nginx, PostgreSQL, Gunicorn на Ubuntu 20
Создание контейнера Django
Находясь в папке 'app' создадим файл контейнера с названием Dockerfile. Поместим в него следующее содержимое:
vim Dockerfile
# образ на основе которого создаём контейнер
FROM python:3.8.6-alpine
# рабочая директория внутри проекта
WORKDIR /usr/src/app
# переменные окружения для python
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# устанавливаем зависимости
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# копируем содержимое текущей папки в контейнер
COPY . .
Обратите внимание на следующие вещи:
- Вы можете использовать другую версию Python. Репозиторий образов находится тут;
- Переменная окружения 'PYTHONDONTWRITEBYTECODE' - Python не будет создавать файлы кэша .pyc;
- 'PYTHONUNBUFFERED' - не помещает в буфер потоки stdout и stderr.
Теперь перейдем на уровень выше (в папку 'project/') и создадим в ней файл 'docker-compose.yml' со следующим содержимым:
cd ..
vim docker-compose.yml
version: '3.8'
services:
web:
# Берем Dockerfile из каталога app
build: ./app
# Запускаем тестовый сервер
command: python manage.py runserver 0.0.0.0:8000
# куда будут помещены данные из каталога app
volumes:
- ./app/:/usr/src/app/
# Открываем порт 8000 внутри и снаружи
ports:
- 8000:8000
# Файл содержащий переменные для контейнера
env_file:
- ./.env.dev
Обратите внимание на значение 'version'. Если вы используете старую версию Docker - вам может потребоваться ее изменить.
В самом конце файла мы разместили значение 'env_file', который еще не создали. В зависимости от того как вы ведете проект на Django - вы можете организовать процесс иначе чем описано далее. В этом файле будут размещены переменные, которые по разным причинам могут быть изменены. Это, например, может быть переменная с паролем, которая для каждого проекта будет принимать разное значение.
Нам нужно вернуться в каталог 'app', где находится django, и изменить следующие значения в файле 'settings.py':
- SECRET_KEY - ключ 'безопасности' через который работают такие вещи как csrf_token.
- DEBUG - будет ли сервер отображать ошибки или просто выводить 404;
- ALLOWED_HOSTS - с каких хостов можно будет подключаться к Django.
Мы по-разному определим значения этих переменных для разных версий Django. Версию вашего Django можно посмотреть, например, так:
cat app/requirements.txt
Откроем файл settings.py:
vim app/django_project/settings.py
Если версия Django меньше или равна 3, то в settings.py уже используется библиотека os и вы должны будете заменить значения следующих переменных так:
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = int(os.environ.get('DEBUG', default=0))
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS').split(' ')
Начиная с версии Django 3.1 вместо os используется библиотека pathlib и нам нужно выполнить дополнительный импорт:
from os import environ
SECRET_KEY = environ.get('SECRET_KEY')
DEBUG = int(environ.get('DEBUG', default=0))
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS').split(' ')
Теперь, в корне проекта, каталоге 'project', создадим файл '.env.dev' со следующим содержимым:
vim .env.dev
DEBUG=1
SECRET_KEY=secretkey
ALLOWED_HOSTS=localhost 127.0.0.1
Настройки в файле env.dev говорят, что сервер будет запускаться в режиме "отладки" и принимать подключения с 127.0.0.1 или localhost.
Теперь, каждый раз выполняя сборку контейнеров, у нас будут выполняться следующий процесс:
- При запуске контейнера будет читаться файл .env.dev, содержащий какие-то значения;
- Значения из файла будут помещены как глобальные переменные проекта (переменные окружения);
- Django, через библиотеку os.eviron, читает некоторые переменные из окружения и подставляет где нужно.
Таким образом, если мы захотим поменять настройки, нам не придется еще раз редактировать 'settings.py' или 'docker-compose.yml'.
Теперь выполним билд проекта (из папки 'project'), при котором создастся нужный контейнер:
docker-compose build
Запустим контейнер:
docker-compose up -d
Проверьте открывается ли страница по адресу 'http://localhost:8000/' или можете выполнить команду:
curl http://localhost:8000
Если у вас какие-то ошибки - проверьте соответствует ли у вас структура папок:
Дополнительно вы можете посмотреть на вывод логов докера:
docker-compose logs -f
PostgreSQL
Что бы мы могли работать с PostgreSQL нам нужно создать пользователя и базу данных, а так же установить некоторые зависимости для самого Django. За счет них мы сможем выполнять подключения к базе и выполнять запросы.
Для создания базы данных мы обновим файл docker-compose.yml добавив блок под названием db. В этом блоке мы пропишем учетные данные пользователя и имя базы (образ Postgres подхватывает эти переменные сам) и том, на котором будем хранить базу. Обратите внимание на версию образа postgres, возможно вы захотите ее изменить:
vim docker-compose.yml
version: '3.8'
services:
web:
build: ./app
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./app/:/usr/src/app/
ports:
- 8000:8000
env_file:
- ./.env.dev
db:
# Образ и версия базы, которую мы будем использовать
image: postgres:12.0-alpine
# Внешний том(volume) и место где он будет подключен внутри контейнера
volumes:
- postgres_volume:/var/lib/postgresql/data/
environment:
# Учетные данные, которые можно будет переопределить
- POSTGRES_USER=django_user
- POSTGRES_PASSWORD=django_password
- POSTGRES_DB=django_db
volumes:
postgres_volume:
Обычно в контейнерах базы данных не запускают (исключения локальная разработка и тесты). Если вам нужно запускать базу в контейнере, то скорее всего вы будете делать это не единожды. В обычном случае, если не описывать volume для контейнера с базой, каждое пересоздание контейнера приведет к уничтожению базы и данных. В случае выше мы описали создание внешнего тома Docker 'postgres_volume', который будет хранить данные на случай пересоздания контейнера.
Django тоже нужно знать какой логин и пароль, а так же имя базы, использует Postgres. Что бы мы этим могли удобно управлять - обновим файл где храним переменные окружения:
vim .env.dev
DEBUG=1
SECRET_KEY=foo
ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
POSTGRES_ENGINE=django.db.backends.postgresql
POSTGRES_DB=django_db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=django_password
POSTGRES_HOST=db
POSTGRES_PORT=5432
На данные момент получается, что в 'docker-compose.yml' мы используем логин и пароль, а так же и файле переменных для Django. Если вам удобно, то вы можете создать отдельный файл переменных для базы данных или использовать один '.env.dev' для двоих контейнеров.
Нам еще раз нужно отредактировать файл 'setting.py', и изменить значения переменной 'DATABASES', хранящие настройки для баз данных:
vim app/django_project/settings.py
Если вы используете Django 3.0 или меньше
DATABASES = {
'default': {
'ENGINE': os.environ.get('POSTGRES_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.environ.get('POSTGRES_DB', os.path.join(BASE_DIR, 'db.sqlite3')),
'USER': os.environ.get('POSTGRES_USER', 'user'),
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'password'),
'HOST': os.environ.get('POSTGRES_HOST', 'localhost'),
'PORT': os.environ.get('POSTGRES_PORT', '5432'),
}
}
Если у вас Django 3.1 или больше:
DATABASES = {
'default': {
'ENGINE': environ.get('POSTGRES_ENGINE', 'django.db.backends.sqlite3'),
'NAME': environ.get('POSTGRES_DB', BASE_DIR / 'db.sqlite3'),
'USER': environ.get('POSTGRES_USER', 'user'),
'PASSWORD': environ.get('POSTGRES_PASSWORD', 'password'),
'HOST': environ.get('POSTGRES_HOST', 'localhost'),
'PORT': environ.get('POSTGRES_PORT', '5432'),
}
}
Установка зависимостей, которые требуются для работы Django с Postgres, выполняется в двух местах. Первое - через менеджер пакетов контейнера мы установим 'postgresql-dev', 'gcc', 'python3-dev' и 'musl-dev':
vim app/Dockerfile
FROM python:3.8.6-alpine
# рабочая директория внутри проекта
WORKDIR /usr/src/app
# переменные окружения
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Устанавливаем зависимости для Postgre
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
# устанавливаем зависимости
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# копируем содержимое текущей папки в контейнер
COPY . .
Вторым шагом мы должны воспользоваться менеджером пакетов для Python - pip, который установит библиотеку 'psycopg2-binary'. Эта библиотека, в дальнейшем, должна быть добавлена в файл 'requirements.txt'.
Вы можете установить библиотеку правильным способом, через pip и виртуальное окружение:
source app/env/bin/activate
pip install psycopg2-binary
pip freeze > app/requirements.txt
Или не очень правильным, добавив жесткую версию библиотеки, как в команде ниже:
echo "psycopg2-binary==2.8.6" >> app/requirements.txt
Проблема прописывания версии напрямую в том, что у вас может быть проблема с зависимостями.
Выполним билд и запуск наших контейнеров еще раз:
docker-compose up -d --build
У вас не должно появляться ошибок. Вы можете открыть страницу 'http://127.0.0.1' или выполнить запрос curl для проверки работы Django:
curl localhost:8000
Теперь проверим работу подключения к базе данных. Выполним миграции при которых будут созданы таблицы самим Django:
docker-compose exec web python manage.py migrate --noinput
Мы можем подключиться к контейнеру с базой данных и посмотреть созданные базы, пользователей и таблицы. Замените пользователя и название базы если использовали другие:
docker-compose exec db psql --username=django_user --dbname=django_db
Затем, последовательно, выполните следующие команды:
\l
\dt
У нас вывелась база для джанго (2) и список созданных через миграцию таблиц (1). Выйдем из режима работы с базой:
\q
Проверить, что у нас был создан том для базы можно так:
docker volume ls
docker volume inspect project_postgres_volume
Дополнительные настройки для PostgreSQL
Мы можем создать скрипт, который будет проверять запущен ли сервер с базой, дожидаться этого ответа и выполнять миграции автоматически. Для этого мы создадим файл entrypoint.sh по следующему пути:
vim app/entrypoint.sh
Поместите в него следующий текст:
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
# если база еще не запущена
echo "DB not yet run..."
# Проверяем доступность хоста и порта
while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do
sleep 0.1
done
echo "DB did run."
fi
# Удаляем все старые данные
python manage.py flush --no-input
# Выполняем миграции
python manage.py migrate
exec "$@"
Обратите внимание на то, что у вас может получится вечный цикл, если контейнер с postgres не запустится вообще. Условие, проверяющее является ли база 'postgres', нужно если вы захотите создать отдельный контейнер без использования PostgreSQL.
Для работы этого скрипта нам изменить права на файл:
chmod 774 app/entrypoint.sh
Так же изменим Dockerfile и добавим запуск этого скрипта при запуске контейнера:
echo 'ENTRYPOINT ["/usr/src/app/entrypoint.sh"]' >> app/Dockerfile
Не забудьте добавить переменную DATABASE в файл с переменными:
echo 'DATABASE=postgres' >> .env.dev
Перед тестированием шагов выше - остановите контейнеры и удалите том, который был создан для базы раннее:
docker-compose down
docker volume ls
docker volume rm project_postgres_volume
Создадим образы и запустим контейнеры:
docker-compose up -d --build
Миграции должны выполнится автоматически:
docker-compose logs
Если вы получили ошибку (показанную ниже) или не увидели миграций, то скорее всего это связано с тем что вы не удалили предыдущий том:
- FATAL: database "django_database" does not exist
Создание первого приложения во фреймворке Python Django 3
Gunicorn
Для работы между Django и Nginx используется gunicorn. В момент запроса пользователя, в первую очередь, он будет обработан Nginx. Nginx решит, к какому типу относится запрос (статический/динамический) и в случае динамического содержимого обратиться к Gunicorn.
Сам Gunicorn, так же как и библиотеку psycopg2 нужно установить и добавить в 'requerements.txt' двумя путями.
Рекомендованный способ - через виртуальное окружение и pip:
source app/env/bin/activate
pip install gunicorn
pip freeze > app/requirements.txt
Второй способ - просто добавьте имя и версию библиотеки без установки:
echo 'gunicorn==20.0.4' >> app/requirements.txt
Сам gunicorn, чаще всего, будет использоваться в продакшене. Что бы было удобно использовать образ для локальной разработки и продакшена создадим второй файл с названием 'docker-compose.prod.yml'. Он будет аналогичен предыдущему образу за исключением отмеченных моментов:
vim docker-compose.prod.yml
version: '3.8'
services:
web:
# Берем Dockerfile из каталога app
build: ./app
# Запускаем сервер gunicorn
command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
# Открываем порт 8000 внутри и снаружи
ports:
- 8000:8000
env_file:
- ./.env.prod
# Дожидаемся запуска контейнера db
depends_on:
- db
db:
# Образ и версия базы, которую мы будем использовать
image: postgres:12.0-alpine
# Внешний том(volume) и место где он будет подключен внутри контейнера
volumes:
- postgres_volume:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
volumes:
postgres_volume:
Где:
- Мы запускаем через gunicorn наш wsgi.py(создается автоматически) файл в папке django_project;
- Новый файл, который будет хранить наши переменные для рабочей среды;
- Дожидаемся загрузки базы;
- Переменные для базы данных.
Мы так же убрали строку, где пробрасывали содержимое папки app в контейнер т.к. это не понадобится.
В файл '.env.prod' поместим переменные, аналогичные 'env.dev. Подразумевается, что для локально и для продакшена вы используете разные переменные:
cp .env.dev .env.prod
Если раннее вы не создавали файл с переменными для баз данных - то создайте. Или выполните копирование:
cp .env.dev.db .env.prod.db
Содержимое этого файла должно быть таким:
vim .env.prod.db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=django_password
POSTGRES_DB=django_db
Выполним остановку предыдущих контейнеров и билд нового:
docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build
Все тесты должны проходить успешно.
На данный момент структура созданных файлов и папок следующая:
Создание Dockerfile для продакшена
Ранее, когда мы создавали скрипт для проверки базы и миграций, мы выполняли метод flush, который очищал базу. Что бы этого не происходило, а так же что бы миграции не накатывались при каждом запуске контейнера - создадим файл 'entrypoint.prod.sh' со следующим содержимым:
vim app/entrypoint.prod.sh
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Postgres еще не запущен..."
# Проверяем доступность хоста и порта
while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do
sleep 0.1
done
echo "Postgres запущен"
fi
exec "$@"
Изменим разрешение на файл:
chmod 774 app/entrypoint.prod.sh
Теперь создадим образ докера для создания контейнера с Django. В примере ниже будет создан мульти-образ для экономии месте. Builder - это временный образ с помощью которого будут созданы бинарные файлы Python. После создания образа builder с него будут скопированы файлы в наш основной образ:
vim app/Dockerfile.prod
###########
# BUILDER #
###########
FROM python:3.8.3-alpine as builder
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# установка зависимостей
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev
RUN pip install --upgrade pip
# проверка кода через линтер
RUN pip install flake8
COPY . .
RUN flake8 --ignore=E501,F401 /usr/src/app/django_project
# установка зависимостей
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt
#########
# FINAL #
#########
FROM python:3.8.3-alpine
# создаем директорию для пользователя
RUN mkdir -p /home/app
# создаем отдельного пользователя
RUN addgroup -S app && adduser -S app -G app
# создание каталога для приложения
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
# установка зависимостей и копирование из builder
RUN apk update && apk add libpq
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*
# копирование entrypoint-prod.sh
COPY ./entrypoint.prod.sh $APP_HOME
# копирование проекта Django
COPY . $APP_HOME
# изменение прав для пользователя app
RUN chown -R app:app $APP_HOME
# изменение рабочего пользователя
USER app
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
В образе мы так же создаем пользователя и группу app. Это делается для того, что бы не использовать пользователя 'root' , который используется по умолчанию Docker.
Так же обратите внимание на строку с flake8. С помощью этого пакета будет выполнятся проверка вашего приложения и в случае ошибок в стиле кода - билд не будет выполнен. Возможно вы захотите убрать эту строку.
Изменим файл 'docker-compose.prod.yml' что бы он использовал новый файл Docker:
vim docker-compose.prod.yml
version: '3.8'
services:
web:
# Берем Dockerfile из каталога app
build:
context: ./app
dockerfile: Dockerfile.prod
# Запускаем сервер gunicorn
command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
# Открываем порт 8000 внутри и снаружи
ports:
- 8000:8000
env_file:
- ./.env.prod
# Дожидаемся запуска db
depends_on:
- db
db:
# Образ и версия базы, которую мы будем использовать
image: postgres:12.0-alpine
# Внешний том(volume) и место где он будет подключен внутри контейнера
volumes:
- postgres_volume:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
volumes:
postgres_volume:
Протестируем:
docker-compose -f docker-compose.prod.yml down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
Nginx
Nginx будет работать со статическими файлами и будет обращаться к Gunicorn. Первое, что мы сделаем - добавим следующие строки в файл 'docker-compose.prod.yml':
vim docker-compose.prod.yml
nginx:
build: ./nginx
ports:
- 1337:80
depends_on:
- web
В примере выше мы открываем порт 1337 для посетителей сайта, который будет перенаправлять пакеты на 80 порт nginx. Вы можете изменить порт 1337 на любой другой.
Создадим папку, в которой будут хранится конфигурационный файл nginx и файл Docker:
mkdir nginx
Создадим файл конфигурации Nginx со следующим содержимым:
vim nginx/nginx.conf
upstream django_proj {
# Список бэкэнд серверов для проксирования
server web:8000;
}
server {
listen 80;
# Параметры проксирования
location / {
# Если будет открыта корневая страница
# все запросу пойдут к одному из серверов
# в upstream django_proj
proxy_pass http://django_proj;
# Устанавливаем заголовки
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
# Отключаем перенаправление
proxy_redirect off;
}
}
Что бы файл конфигурации попал в контейнер мы должны создать отдельный Dockerfile:
vim nginx/Dockerfile
FROM nginx
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/
После создания образа Nginx мы уже не будем обращаться к Django напрямую, а это значит что мы можем закрыть для него доступ через перенаправление портов. Мы можем разрешить обращаться к Django только сервисам Docker. Для этого заменим 'port' на 'expose':
vim docker-compose.prod.yml
expose:
- 8000
Запустим контейнеры еще раз и убедимся, что они работают:
docker-compose -f docker-compose.prod.yml down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
Теперь у нас будет отвечать не Django, а nginx:
curl localhost:8000
curl localhost:1337
На данный момент у вас должна быть следующая структура папок и файлов:
Настройка для статических и медиа файлов
Для работы с файлами, которые не изменяются Django, используется понятие статических файлов. Так же есть понятие медиа файлов, которые определяют файлы загруженные через Django. Настройки для этих файлов выполняются в 'settings.py'. Нам нужно определить некоторые переменные для разных версий Django:
vim app/django_project/settings.py
Для Django 3.0 и меньше:
# То как статический файл будет отображаться в url
# Пример /static/1.jpg
STATIC_URL = "/static/"
# По какому пути можно будет найти файлы
STATIC_ROOT = os.path.join(BASE_DIR, "static")
# Аналогично static файлам
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
Для Django 3.1 и больше:
# То как статический файл будет отображаться в url
# Пример /static/1.jpg
STATIC_URL = "/static/"
# По какому пути можно будет найти файлы
STATIC_ROOT = BASE_DIR / "static"
# Аналогично static файлам
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
Обратите внимание, что у вас уже будет прописан 'STATIC_URL' самим Django и вы должны избежать дублирования этой переменной. Эта переменная будет иметь значение аналогичное написанному выше.
Эти папки будут работать в контейнерах по пути '/home/app/web/static' и /home/app/web/media'. Так как эти файлы могут быть использованы как Nginx, так и Django мы создадим 2 тома Docker и подключим их к обоим контейнерам:
vim docker-compose.prod.yml
version: '3.8'
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn django_project.wsgi:application --bind 0.0.0.0:8000
# Открываем порт 8000 наружу
expose:
- 8000
# Подключаем статические и медиа файлы
volumes:
- static_volume:/home/app/web/static
- media_volume:/home/app/web/media
env_file:
- ./.env.prod
# Дожидаемся запуска db
depends_on:
- db
db:
image: postgres:12.0-alpine
volumes:
- postgres_volume:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx:
build: ./nginx
# Подключаем статические и медиа файлы
volumes:
- static_volume:/home/app/web/static
- media_volume:/home/app/web/media
ports:
- 1337:80
depends_on:
- web
volumes:
postgres_volume:
static_volume:
media_volume:
Чтобы у нас не было проблем с правами (эти папки будут проброшены с правами для root) мы должны создать аналогичные папки в контейнере используя Dockerfile.prod. Допишите следующие инструкции в следующем месте:
vim app/Dockerfile.prod
RUN mkdir $APP_HOME/static
RUN mkdir $APP_HOME/media
Так же нужно изменить файл конфигурации Nginx что он тоже обращался к этим папкам:
vim nginx/nginx.conf
upstream django_proj {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://django_proj;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
# подключаем статические файлы
location /static/ {
alias /home/app/web/static/;
}
# подключаем медиа файлы
location /media/ {
alias /home/app/web/media/;
}
}
Тестирование работы образов и возможные ошибки
Выполним остановку предыдущих контейнеров и создадим новые. При этом выполним миграции и соберем все статические файлы:
docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
В файле 'docker-compose.prod.yml' мы прописали что nginx будет работать на 1337 порту. Проверим, что сервер отвечает:
curl localhost:1337
Я могу открыть сайт с любого компьютера в локальной сети т.к. в файле '.env.prod' прописано несколько хостов с которых может быть принято соединение:
ALLOWED_HOSTS=localhost 127.0.0.1 192.168.2.121 [::1]
Если у вас будет появляться ошибка с 'ALLOWED_HOSTS', то вы можете установить следующее значение разрешающее все подключения с любого адреса:
ALLOWED_HOSTS=*
Один из способов проверки работы статических файлов - это открытие панель администрирования по адресу 'http://localhost/admin/'. Фрагмент, помеченный цифрой 1, говорит об ошибке. Фрагмент под цифрой 2 - нормальная работа:
Нам нужно убедиться, что везде верно прописаны настройки для статических файлов и следующая команда, которая собирает такие файлы воедино, выполнена:
docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
Эти файлы должны отображаться на обоих контейнерах т.к. они работают с одним томом:
docker exec project_nginx_1 ls /home/app/web/static
docker exec project_web_1 ls /home/app/web/static
Если файлов нет - возможно у вас проблемы с правами. Сравните файл Docker. Если нет таких папок, то с томом в файле docker-compose.prod.yml. Ошибка так же может быть в файле конфигурации nginx.conf или в файле setting.py Django.
Для проверки медиа файлов, которые будут загружаться через Django, вы можете создать пустой файл:
docker exec project_nginx_1 touch /home/app/web/media/test.txt
Далее выполнить к нему запрос:
curl localhost:1337/media/test.txt
Если вернется ошибка 404 - то стоит посмотреть на те же места, что в случае статических файлов.
Чаще всего помогает просмотр логов через докер:
docker-compose logs
В некоторых случаях стоит удалить том с базой, который создавался ранее.
Варианты запуска
У вас есть 2 основных файла Docker:
- docker-compose.yml - для локальной разработки. Запускает только Django и Postgres. В случае этого запуска база данных будет очищена (файл 'app/entrypoint.sh');
- docker-compose.prod.yml - для запуска на релизных версиях. Запускает Django, Postgres, Nginx и Gunicorn. Номер порта, для Nginx, указан в самом файле (1337).
В каждом файле docker-compose используются файлы, с переменными окружений, которые начинаются на '.env'. В случае локального образа - это один файл (переменные для Postgres прописаны в docker-compose.yml), в случае продакшена это два файла. Все файлы имеют одинаковые переменные, значение которых можно изменить:
- DEBUG - принимает значение от 0 до 1. В случае 1 будут выводится сообщения об ошибках при открытии страниц сайта;
- SECRET_KEY - ключ безопасности, который чаще всего имеет разное значение в случае локального запуска и продакшена;
- ALLOWED_HOSTS - имена или ip адреса хостов через пробел, через которые можно подключиться к Django;
- POSTGRES_ENGINE - модуль для работы с базой. Обычно не изменяется;
- POSTGRES_DB - название базы, которую хотите использовать;
- POSTGRES_USER - пользователь;
- POSTGRES_PASSWORD - пароль;
- POSTGRES_HOST - название контейнера (используется db);
- POSTGRES_PORT - порт;
- DATABASE - если значение не равно postgres не будет осуществлена проверка работы базы данных.
Для запуска образа для разработки выполните:
docker-compose up -d --build
В случае образа для релиза выполните:
docker-compose -f docker-compose.prod.yml up -d --build
В отличие от обычного образа, в случае продакшена нужно дополнительно выполнять миграции и сбор статических файлов:
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
...