В этом уроке мы создадим новый проект Django, используя Docker и PostgreSQL. Django поставляется со встроенной поддержкой SQLite, но даже для локальной разработки лучше использовать «настоящую» базу данных, такую как PostgreSQL, которая соответствует производственной.
Можно запускать PostgreSQL локально, используя такой инструмент, как Postgres.app, однако сегодня среди многих разработчиков предпочтительным является использование Docker, инструмента для создания изолированных операционных систем. Проще всего представлять это как виртуальную среду, которая содержит все необходимое для нашего проекта Django: зависимости, базы данных, службы кэширования и любые другие необходимые инструменты.
Основная причина использования Docker заключается в том, что он полностью устраняет любые проблемы, связанные с локальной разработкой. Вместо того, чтобы беспокоиться о том, какие программные пакеты установлены или работают с локальной базой данных вместе с проектом, вы просто запускаете образ Docker всего проекта. Лучше всего то, что этим можно поделиться в группах и значительно упростить разработку команды.
Первым шагом является установка настольного приложения Docker для вашего локального компьютера:
Первоначальная загрузка Docker может занять некоторое время для загрузки.
После завершения установки Docker мы можем подтвердить, что запущена правильная версия. В вашем терминале запустите команду docker —version.
$ docker --version Docker version 19.03.2, build 6a30dfc
Docker Compose — это дополнительный инструмент, который автоматически включается в загрузку Docker для Mac и Windows. Однако, если вы используете Linux, вам нужно будет добавить его вручную. Вы можете сделать это, выполнив команду sudo pip install docker-compose после завершения установки Docker.
Создайте новый каталог проекта вместе с новым проектом Django:
$ mkdir django-on-docker && cd django-on-docker $ mkdir app && cd app $ python3.8 -m venv env $ source env/bin/activate (env)$ pip install django==3.0.7 (env)$ django-admin.py startproject hello_django . (env)$ python manage.py migrate (env)$ python manage.py runserver
Перейдите по адресу http://localhost:8000/ для просмотра экрана приветствия Django. Остановите сервер и выйдите из виртуальной среды. Теперь у нас есть простой проект Django для работы.
Создайте файл requirements.txt в каталоге app и добавьте Django в качестве зависимости:
Django==3.0.7
Поскольку мы перейдем будем использовать Postgres в качестве БД для проекта, удалите файл db.sqlite3 из каталога app.
Ваша директория проекта должна выглядеть так:
└── app ├── hello_django │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py └── requirements.txt
Надеюсь, Docker завершил установку к этому моменту. Чтобы убедиться, что установка прошла успешно, закройте локальный сервер с помощью Control + c, а затем введите в командной строке docker run hello-world. Вы должны увидеть ответ вроде этого:
$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world d1725b59e92d: Pull complete Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image whi ch runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
В Docker есть две важные концепции: образы (images) и контейнеры (containers).
Другими словами, образ (image) описывает, что произойдет, а контейнер (container) — это то, что фактически выполняется.
Для настройки образов и контейнеров в Docker мы используем два файла: Dockerfile и docker-compose.yml.
Dockerfile содержит список инструкций для образа, иначе говоря, что на самом деле происходит в среде контейнера.
Создадим новый файл Dockerfile.
(env) $ touch Dockerfile
Затем добавьте следующий код в него.
# pull official base image FROM python:3.8.3-alpine # set work directory WORKDIR /usr/src/app # set environment variables ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # install dependencies RUN pip install --upgrade pip COPY ./requirements.txt . RUN pip install -r requirements.txt # copy project COPY . .
В верхней строке мы используем официальный образ Docker для Python 3.8. Далее мы создаем две переменные окружения.
Затем мы устанавливаем рабочий каталог вместе с двумя переменными среды:
PYTHONUNBUFFERED гарантирует, что наш вывод консоли выглядит знакомым и не буферизируется Docker, что нам не нужно. PYTHONDONTWRITEBYTECODE означает, что Python не будет пытаться создавать файлы .pyc, которые мы также не желаем.
Наконец, мы обновили pip, скопировали файл requirements.txt, установили зависимости и скопировали сам проект Django.
Мы не можем запустить Docker-контейнер, пока у нас не будет созданного образа, поэтому давайте сделаем это, создав его.
(env) $ docker build .
В случае успеха у вас должно быть что то типа такого.
Sending build context to Docker daemon 162.3kB Step 1/8 : FROM python:3.7 3.7: Pulling from library/python c7b7d16361e0: Pull complete b7a128769df1: Pull complete 1128949d0793: Pull complete 667692510b70: Pull complete bed4ecf88e6a: Pull complete 8a8c75f3996a: Pull complete 10b7379e5573: Pull complete ca1b6fe24628: Pull complete 9a90211ec083: Pull complete Digest: sha256:fc0a398e1987fb1e58909053c11630e06adb3df265fe693391779020b9253f5e Status: Downloaded newer image for python:3.7 ---> 9fa56d0addae Step 2/8 : ENV PYTHONDONTWRITEBYTECODE 1 ---> Running in 5e7a7983814d Removing intermediate container 5e7a7983814d ---> 3aff2533de96 .... Successfully built 98329412f14c
Далее нам нужен новый файл docker-compose.yml. Он говорит Docker, как запустить наши Docker-контейнеры. У нас будут 2 контейнера. Один для базы, другой для приложения.
(app) $ touch docker-compose.yml
С начало добавим в него один контейнер для приложения:
version: '3.7' 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
Обновите переменные SECRET_KEY, DEBUG и ALLOWED_HOSTS в settings.py:
SECRET_KEY = os.environ.get("SECRET_KEY") DEBUG = int(os.environ.get("DEBUG", default=0)) # 'DJANGO_ALLOWED_HOSTS' должен быть в виде одной строки с хостами разделенными символом пробела # Для примера: 'DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]' ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
Затем создайте файл .env.dev в корне проекта для хранения переменных среды для разработки:
DEBUG=1 SECRET_KEY=foo DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
Собираем образ командой:
$ docker-compose build
Как только образ будет собран, запускаем контейнер:
$ docker-compose up -d
Далее нужно перейти по адресу http://localhost:8000/, чтобы снова увидеть экран приветствия и убедиться что все работает.
Проверьте наличие ошибок в журналах, если это не работает, через команду:
docker-compose logs -f
Докер настроен!
Чтобы настроить Postgres, нам нужно добавить новый сервис в файл docker-compose.yml, обновить настройки Django и установить Psycopg2.
Сначала добавим новый сервис db в docker-compose.yml:
version: '3.7' 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 depends_on: - db db: image: postgres:12.0-alpine volumes: - postgres_data:/var/lib/postgresql/data/ environment: - POSTGRES_USER=hello_django - POSTGRES_PASSWORD=hello_django - POSTGRES_DB=hello_django_dev volumes: postgres_data:
Чтобы сохранить данные за пределами контейнера, мы настроили том (volume). Этот конфиг будет связывать postgres_data с каталогом «/var/lib/postgresql/data/» в контейнере.
Мы также добавили ключ среды, чтобы определить имя для базы данных по умолчанию и установить имя пользователя и пароль.
Поэтому внесем соотвествующие изменения в файл .env.dev :
DEBUG=1 SECRET_KEY=foo DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] SQL_ENGINE=django.db.backends.postgresql SQL_DATABASE=hello_django_dev SQL_USER=hello_django SQL_PASSWORD=hello_django SQL_HOST=db SQL_PORT=5432
Затем обновите файл settings.py, чтобы указать, что мы будем использовать PostgreSQL, а не SQLite.
DATABASES = { "default": { "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"), "NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")), "USER": os.environ.get("SQL_USER", "user"), "PASSWORD": os.environ.get("SQL_PASSWORD", "password"), "HOST": os.environ.get("SQL_HOST", "localhost"), "PORT": os.environ.get("SQL_PORT", "5432"), } }
Здесь база данных настраивается на основе переменных среды, которые мы только что определили. Обратите внимание на значения по умолчанию.
Внесем изменения в Dockerfile, чтобы установить соответствующие пакеты, необходимые для Psycopg2:
# pull official base image FROM python:3.8.3-alpine # set work directory WORKDIR /usr/src/app # set environment variables ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # install psycopg2 dependencies RUN apk update \ && apk add postgresql-dev gcc python3-dev musl-dev # install dependencies RUN pip install --upgrade pip COPY ./requirements.txt . RUN pip install -r requirements.txt # copy project COPY . .
Добавьте Psycopg2 в файл requirements.txt:
Django==3.0.7 psycopg2-binary==2.8.5
Соберем новый образ и запустим два контейнера:
$ docker-compose up -d --build
Запустим миграцию:
$ docker-compose exec web python manage.py migrate --noinput
Если получите следующую ошибку:
django.db.utils.OperationalError: FATAL: database "hello_django_dev" does not exist
Остановите контейнер командой docker-compose down -v, чтобы удалить тома вместе с контейнерами. Затем заново создайте образы, запустите контейнеры и примените миграции.
Убедимся, что все таблицы Django по умолчанию были созданы:
$ docker-compose exec db psql --username=hello_django --dbname=hello_django_dev psql (12.0) Type "help" for help. hello_django_dev=# \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ------------------+--------------+----------+------------+------------+------------------------------- hello_django_dev | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | postgres | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | template0 | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | =c/hello_django + | | | | | hello_django=CTc/hello_django template1 | hello_django | UTF8 | en_US.utf8 | en_US.utf8 | =c/hello_django + | | | | | hello_django=CTc/hello_django (4 rows) hello_django_dev=# \c hello_django_dev You are now connected to database "hello_django_dev" as user "hello_django". hello_django_dev=# \dt List of relations Schema | Name | Type | Owner --------+----------------------------+-------+-------------- public | auth_group | table | hello_django public | auth_group_permissions | table | hello_django public | auth_permission | table | hello_django public | auth_user | table | hello_django public | auth_user_groups | table | hello_django public | auth_user_user_permissions | table | hello_django public | django_admin_log | table | hello_django public | django_content_type | table | hello_django public | django_migrations | table | hello_django public | django_session | table | hello_django (10 rows) hello_django_dev=# \q
Вы также можете проверить, что том (volume) был создан, запустив команду:
$ docker volume inspect django-on-docker_postgres_data
Вы должны увидеть что-то похожее на:
[ { "CreatedAt": "2020-06-13T18:43:56Z", "Driver": "local", "Labels": { "com.docker.compose.project": "django-on-docker", "com.docker.compose.version": "1.25.4", "com.docker.compose.volume": "postgres_data" }, "Mountpoint": "/var/lib/docker/volumes/django-on-docker_postgres_data/_data", "Name": "django-on-docker_postgres_data", "Options": null, "Scope": "local" } ]
Затем добавим файл entrypoint.sh в каталог проекта app, чтобы проверить работоспособность Postgres перед применением миграций и запуском сервера разработки Django:
#!/bin/sh if [ "$DATABASE" = "postgres" ] then echo "Waiting for postgres..." while ! nc -z $SQL_HOST $SQL_PORT; do sleep 0.1 done echo "PostgreSQL started" fi python manage.py flush --no-input python manage.py migrate exec "$@"
Обновим локальные права доступа к файлу:
$ chmod +x app/entrypoint.sh
Затем обновим Dockerfile, чтобы скопировать файл entrypoint.sh и запустите его как команду точки входа Docker:
# pull official base image FROM python:3.8.3-alpine # set work directory WORKDIR /usr/src/app # set environment variables ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # install psycopg2 dependencies RUN apk update \ && apk add postgresql-dev gcc python3-dev musl-dev # install dependencies RUN pip install --upgrade pip COPY ./requirements.txt . RUN pip install -r requirements.txt # copy entrypoint.sh COPY ./entrypoint.sh . # copy project COPY . . # run entrypoint.sh ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
Добавим переменную среды DATABASE в .env.dev:
DEBUG=1 SECRET_KEY=foo DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] SQL_ENGINE=django.db.backends.postgresql SQL_DATABASE=hello_django_dev SQL_USER=hello_django SQL_PASSWORD=hello_django SQL_HOST=db SQL_PORT=5432 DATABASE=postgres
Проверьте все снова:
Во-первых, несмотря на добавление Postgres, мы все равно можем создать независимый образ Docker для Django, если для переменной среды DATABASE не задано значение postgres. Чтобы проверить, создайте новый образ и затем запустите новый контейнер:
$ docker build -f ./app/Dockerfile -t hello_django:latest ./app $ docker run -d \ -p 8006:8000 \ -e "SECRET_KEY=please_change_me" -e "DEBUG=1" -e "DJANGO_ALLOWED_HOSTS=*" \ hello_django python /usr/src/app/manage.py runserver 0.0.0.0:8000
Вы должны увидеть страницу приветствия по адресу http://localhost:8006.
Во-вторых, вы можете закомментировать команды очистки (flush) и миграции (migrate) базы данных в сценарии entrypoint.sh, чтобы они не запускались при каждом запуске или перезапуске контейнера:
#!/bin/sh if [ "$DATABASE" = "postgres" ] then echo "Waiting for postgres..." while ! nc -z $SQL_HOST $SQL_PORT; do sleep 0.1 done echo "PostgreSQL started" fi # python manage.py flush --no-input # python manage.py migrate exec "$@"
Вместо этого вы можете запустить их вручную, после того, как контейнеры запустаться, вот так:
$ docker-compose exec web python manage.py flush --no-input $ docker-compose exec web python manage.py migrate
Когда вы закончите, не можете погасить контейнер Docker.
docker-compose down
Просто приостановить контейнер
docker stop CONTAINER ID
Запустить ранее остановленный контейнер
docker start CONTAINER ID
Перегрузить контейнер
docker restart CONTAINER ID
Что бы посмотреть работающие контейнеры
docker ps
Что бы посмотреть вообще все контейнеры
docker ps -a
Посмотреть список всех образов
docker images
Удалить образ
docker rmi CONTAINER ID или docker rmi -f CONTAINER ID
Иногда может понадобиться зайти в работающий контейнер. Для этого нужно запустить команду запуска интерактивной оболочкой bash
docker exec -it CONTAINER ID bash
Двигаясь дальше, в производственную среду, давайте добавим Gunicorn, сервер WSGI промышленного уровня, в файл requirements.txt:
Django==3.0.7 gunicorn==20.0.4 psycopg2-binary==2.8.5
Поскольку мы все еще хотим использовать встроенный сервер Django для разработки, создайте новый файл compose под названием docker-compose.prod.yml для производственной среды:
version: '3.7' services: web: build: ./app command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 ports: - 8000:8000 env_file: - ./.env.prod depends_on: - db db: image: postgres:12.0-alpine volumes: - postgres_data:/var/lib/postgresql/data/ env_file: - ./.env.prod.db volumes: postgres_data:
Если у вас несколько сред, вы можете использовать конфигурационный файл docker-compose.override.yml. При таком подходе вы добавляете базовую конфигурацию в файл docker-compose.yml, а затем используете файл docker-compose.override.yml для переопределения этих параметров конфигурации в зависимости от среды.
Обратите внимание на команду command. Мы используем Gunicorn, а не сервер разработки Django. Мы также удалили том из web, поскольку он нам не нужен. Наконец, мы используем отдельные файлы переменных среды, чтобы определить переменные среды для обеих служб, которые будут переданы в контейнер во время выполнения.
Файл .env.prod:
DEBUG=0 SECRET_KEY=change_me DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] SQL_ENGINE=django.db.backends.postgresql SQL_DATABASE=hello_django_prod SQL_USER=hello_django SQL_PASSWORD=hello_django SQL_HOST=db SQL_PORT=5432 DATABASE=postgres
Файл .env.prod.db:
POSTGRES_USER=hello_django POSTGRES_PASSWORD=hello_django POSTGRES_DB=hello_django_prod
Добавим эти два файла в корневой каталог проекта. Возможно, вы захотите исключить их git, поэтому добавьте их в файл .gitignore.
Убедимся что у нас все контейнеры остановлены (и связанные тома с флагом -v):
$ docker-compose down -v
Затем соберем производственные образы и запустим контейнеры:
$ docker-compose -f docker-compose.prod.yml up -d --build
Убедимся, что база данных hello_django_prod была создана вместе с таблицами Django по умолчанию. Протестируем страницу администратора по адресу http://localhost:8000/admin. Статические файлы больше не загружаются. Это ожидается, так как режим отладки выключен. Мы исправим это в ближайшее время.
Вы заметили, что мы все еще выполняем очистку базы данных (flush)(которая очищает базу данных) и переносим команды при каждом запуске контейнера? Это хорошо в разработке, но давайте создадим новый файл точки входа для производства.
Файл entrypoint.prod.sh:
#!/bin/sh if [ "$DATABASE" = "postgres" ] then echo "Waiting for postgres..." while ! nc -z $SQL_HOST $SQL_PORT; do sleep 0.1 done echo "PostgreSQL started" fi exec "$@"
Обновим права доступа к файлу:
$ chmod +x app/entrypoint.prod.sh
Чтобы использовать этот файл, создайте новый Dockerfile с именем Dockerfile.prod для использования с производственными сборками:
########### # BUILDER # ########### # pull official base image FROM python:3.8.3-alpine as builder # set work directory WORKDIR /usr/src/app # set environment variables ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 # install psycopg2 dependencies RUN apk update \ && apk add postgresql-dev gcc python3-dev musl-dev # lint RUN pip install --upgrade pip RUN pip install flake8 COPY . . RUN flake8 --ignore=E501,F401 . # install dependencies COPY ./requirements.txt . RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt ######### # FINAL # ######### # pull official base image FROM python:3.8.3-alpine # create directory for the app user RUN mkdir -p /home/app # create the app user RUN addgroup -S app && adduser -S app -G app # create the appropriate directories ENV HOME=/home/app ENV APP_HOME=/home/app/web RUN mkdir $APP_HOME WORKDIR $APP_HOME # install dependencies 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/* # copy entrypoint-prod.sh COPY ./entrypoint.prod.sh $APP_HOME # copy project COPY . $APP_HOME # chown all the files to the app user RUN chown -R app:app $APP_HOME # change to the app user USER app # run entrypoint.prod.sh ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
Здесь мы использовали многоэтапную сборку (multi-stage build) Docker, чтобы уменьшить окончательный размер образа. По сути, builder — это временный образ, которое используется для сборки Python. Затем он копируются в конечный производственный образ, а образ builder отбрасывается.
Вы заметили, что мы создали пользователя без полномочий root? По умолчанию Docker запускает контейнерные процессы как root внутри контейнера. Это плохая практика, поскольку злоумышленники могут получить root-доступ к хосту Docker, если им удастся вырваться из контейнера. Если вы root в контейнере, вы будете root на хосте.
Обновите web сервис в файле docker-compose.prod.yml для сборки с помощью Dockerfile.prod:
web: build: context: ./app dockerfile: Dockerfile.prod command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 ports: - 8000:8000 env_file: - ./.env.prod depends_on: - db
Проверим как все работает:
$ 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, чтобы он действовал как обратный прокси-сервер для Gunicorn для обработки клиентских запросов, а также для обслуживания статических файлов.
Добавим сервис nginx в docker-compose.prod.yml:
nginx: build: ./nginx ports: - 1337:80 depends_on: - web
Затем в локальном корне проекта создайте следующие файлы и папки:
└── nginx ├── Dockerfile └── nginx.conf
Файл Dockerfile:
FROM nginx:1.19.0-alpine RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d
Файл nginx.conf:
upstream hello_django { server web:8000; } server { listen 80; location / { proxy_pass http://hello_django; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } }
Затем обновим сервис web
в docker-compose.prod.yml, заменить ports
на expose
:
build: context: ./app dockerfile: Dockerfile.prod command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 expose: - 8000 env_file: - ./.env.prod depends_on: - db
Теперь порт 8000 открыт только для других сервисов Docker. И это порт больше не будет опубликован на хост-машине.
Проверяем как это работает
$ 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
Убедимся, что приложение запущено и работает по адресу http://localhost:1337.
Структура вашего проекта теперь должна выглядеть так:
├── .env.dev ├── .env.prod ├── .env.prod.db ├── .gitignore ├── app │ ├── Dockerfile │ ├── Dockerfile.prod │ ├── entrypoint.prod.sh │ ├── entrypoint.sh │ ├── hello_django │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── manage.py │ └── requirements.txt ├── docker-compose.prod.yml ├── docker-compose.yml └── nginx ├── Dockerfile └── nginx.conf
Теперь снова остановим контейнеры:
$ docker-compose -f docker-compose.prod.yml down -v
Поскольку Gunicorn является сервером приложений, он не будет обслуживать статические файлы. Итак, настроим обработку статических и мультимедийных файлов
Обновим settings.py:
STATIC_URL = "/staticfiles/" STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
Теперь любой запрос к http://localhost:8000/staticfiles/ * будет обслуживаться из каталога «staticfiles».
Чтобы проверить, сначала песоберем образы и запустим новые контейнеры в обычном режиме. Убедимся, что статические файлы по-прежнему правильно обслуживаются по адресу http://localhost:8000/admin.
Для производственной среды добавьте volume в web и службы nginx в docker-compose.prod.yml, чтобы каждый контейнер имел общий каталог с именем «staticfiles»:
version: '3.7' services: web: build: context: ./app dockerfile: Dockerfile.prod command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 volumes: - static_volume:/home/app/web/staticfiles expose: - 8000 env_file: - ./.env.prod depends_on: - db db: image: postgres:12.0-alpine volumes: - postgres_data:/var/lib/postgresql/data/ env_file: - ./.env.prod.db nginx: build: ./nginx volumes: - static_volume:/home/app/web/staticfiles ports: - 1337:80 depends_on: - web volumes: postgres_data: static_volume:
Нам также необходимо создать папку «/home/app/web/staticfiles» в Dockerfile.prod:
... # create the appropriate directories ENV HOME=/home/app ENV APP_HOME=/home/app/web RUN mkdir $APP_HOME RUN mkdir $APP_HOME/staticfiles WORKDIR $APP_HOME ...
Почему это необходимо?
Docker Compose обычно монтирует именованные тома как root. И поскольку мы используем пользователя без полномочий root, мы получим ошибку отказа в разрешении при запуске команды collectstatic, если каталог еще не существует
Чтобы обойти это, вы можете:
Мы использовали первое.
Затем обновите конфигурацию Nginx для маршрутизации запросов статических файлов в папку «staticfiles»:
upstream hello_django { server web:8000; } server { listen 80; location / { proxy_pass http://hello_django; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /staticfiles/ { alias /home/app/web/staticfiles/; } }
Перезапустим контейнеры
$ 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
Теперь все запросы к http://localhost:1337/staticfiles/ * будут обслуживаться из каталога «staticfiles».
Перейдите по адресу http://localhost:1337/admin и убедитесь, что статические ресурсы загружаются правильно.
Вы также можете проверить в логах командой docker-compose -f docker-compose.prod.yml logs -f что запросы к статическим файлам успешно обрабатываются через Nginx:
Далее снова остановим контейнеры:
$ docker-compose -f docker-compose.prod.yml down -v
Чтобы проверить обработку мультимедийных файлов, начните с создания нового модуля Django:
$ docker-compose up -d --build $ docker-compose exec web python manage.py startapp upload
Добавим новый модуль в INSTALLED_APPS
в settings.py:
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "upload", ]
Внесем изменения в следующие файлы
app/upload/views.py:
from django.shortcuts import render from django.core.files.storage import FileSystemStorage def image_upload(request): if request.method == "POST" and request.FILES["image_file"]: image_file = request.FILES["image_file"] fs = FileSystemStorage() filename = fs.save(image_file.name, image_file) image_url = fs.url(filename) print(image_url) return render(request, "upload.html", { "image_url": image_url }) return render(request, "upload.html")
Добавим директорию «templates», в каталог «app/upload», и добавим новый шаблон upload.html:
{% block content %} <form action="{% url "upload" %}" method="post" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="image_file"> <input type="submit" value="submit" /> </form> {% if image_url %} <p>File uploaded at: <a href="{{ image_url }}">{{ image_url }}</a></p> {% endif %} {% endblock %}
Файл app/hello_django/urls.py:
from django.urls import path from django.conf import settings from django.conf.urls.static import static from upload.views import image_upload urlpatterns = [ path("", image_upload, name="upload"), path("admin/", admin.site.urls), ] if bool(settings.DEBUG): urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Файл app/hello_django/settings.py:
MEDIA_URL = "/mediafiles/" MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles")
Запустим контейнер
$ docker-compose up -d --build
Теперь у вас должна быть возможность загзулить файл на http://localhost:8000/, и затем увидеть этот файл на http://localhost:8000/mediafiles/IMAGE_FILE_NAME.
Для производственной среды добавим новый том volume в сервисы web
и nginx
:
version: '3.7' services: web: build: context: ./app dockerfile: Dockerfile.prod command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000 volumes: - static_volume:/home/app/web/staticfiles - media_volume:/home/app/web/mediafiles expose: - 8000 env_file: - ./.env.prod depends_on: - db db: image: postgres:12.0-alpine volumes: - postgres_data:/var/lib/postgresql/data/ env_file: - ./.env.prod.db nginx: build: ./nginx volumes: - static_volume:/home/app/web/staticfiles - media_volume:/home/app/web/mediafiles ports: - 1337:80 depends_on: - web volumes: postgres_data: static_volume: media_volume:
Создаим каталог /home/app/web/mediafiles в Dockerfile.prod:
... # create the appropriate directories ENV HOME=/home/app ENV APP_HOME=/home/app/web RUN mkdir $APP_HOME RUN mkdir $APP_HOME/staticfiles RUN mkdir $APP_HOME/mediafiles WORKDIR $APP_HOME ...
Снова обновим конфиг Nginx:
upstream hello_django { server web:8000; } server { listen 80; location / { proxy_pass http://hello_django; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /staticfiles/ { alias /home/app/web/staticfiles/; } location /mediafiles/ { alias /home/app/web/mediafiles/; } }
Далее перезапустим контейнеры
$ 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
Проверим как все работает:
В этой статье мы рассмотрели, как создать контейнер для веб-приложения Django с Postgres. Мы также создали готовый к работе файл Docker Compose, который добавляет Gunicorn и Nginx в нашу конфигурацию для обработки статических и мультимедийных файлов. Теперь вы можете проверить производственную настройку локально.
С точки зрения фактического развертывания в производственной среде, вы, вероятно, захотите использовать:
db
и nginx
сервисовДля других советов работы с производственной средой, см эту дисскусию.
Спасибо за чтение
Источники используемые для этой статьи
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…
View Comments
Годная статья. Мне как раз надо было обернуть Django/Postgres в Docker.
Кстати, пропущена одна строка в файле app/hello_django/urls.py:
Из-за отсутствия этой строки возникала ошибка:
Со статиками не понятно, static_volume это вообще откуда? Нужно в корень проекта закинуть статики в эту папку? Но на гите нет такого, да и вообще этой папки нет. Не ясно в общем что делать дальше.
Статья очень слабая, хоть объемная потерял время и не более
При создании каталога проекта вместе с новым проектом Django возникают ошибки
Подскажите как исправить ошибки
Александр, команда sourse это для линукс.
Под Windows нужно писать без sourse, т.е. env\Scripts\activate, либо если назвали не env, то имя вирт.окружения.
python manage.py migrate нужно запускать из созданного каталога проекта hello_django, для этого сделай cd hello_django и уже после сделай migrate.
После команды docker-compose up -d --build, контейнер с postgresql работает исправно, а контейнер с приложением выводит ошибку
https://stackoverflow.com/questions/51508150/standard-init-linux-go190-exec-user-process-caused-no-such-file-or-directory
В первом случае (dev) сразу поменял т.к. нет bash'a. Но второй убил минут 40, просто скопировал с сайта. Лучше все делать за раз)
Шикарная статья! Лучшее, что мне попадалось по теме Django+Docker. Все подробно, поэтапно и не упущен момент настройки прод среды, что часто делают в подобных туториалах.
З.Ы. Было бы удобно иметь это руководство в виде репозитория - чтобы можно было использовать как заготовку для шаблона. Также было бы удобно тем, у кого возникают проблемы и ошибки в процессе проверки примеров из статьи.
Автору огромное спасибо! Статья реально помогла разобраться в теме. После того, как ручками прошел все этапы от начала до конца, смог наконец-то сдать свой учебный проект на котором застрял.
Объясните,пожалуйста,что за переменная os в файле settings.py?
в начале файла напиши
import os
Подскажите,пожалуйста,как создать файл .env.dev в pycharm?
Правой кнопкой кликнуть на папке и вбрать пункт с созданием нового файла. Или прописать "touch .env.dev" в терминале
Какие альтернативные способы существуют для хранения media-файлов тому, который предложен в статье?
Буду рад полезным ссылкам)