Django + Docker для безболісного деплою будь-де

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Усім привіт, мене звати Дмитро, я працюю Python Software Developer в Workconsult Ukraine.

Сьогодні, доречі, наша компанія святкує 7 років з дня заснування!

Вітаю щиро усіх колег з цим днем! Ми гарна команда!

Ця статя розрахована на юних (та не дуже:) падаванів на шляху до становлення джедаєм в розробці web-застосунків за допомогою Django.

Поговоримо про те, коли, рано чи пізно, ви зіткнетеся з питанням — йой, а як же його задеплоїти на сервері, в хмарі, локально, на компі бабусі (потрібне підкреслити)?

Що потрібно зробити, щоб було достатньо однієї команди для безболісного деплою?

Правильно! Docker!

Docker — це магія для розробників, яка допомагає запускати наші програми в спеціальних контейнерах. Контейнери допомагають зробити наш застосунок «переносимим», а це означає, що ми можемо запускати його на будь-якому комп’ютері або хмарному сервері, і не хвилюватись, чи все правильно налаштовано.

Мабуть, ви чули про Docker, але могли стикнутись з невдалими спробами його використання. Не хвилюйтесь, я сам декілька разів попадав у такі ж халепи. Тому я зібрав свій досвід у цій статті, щоб поділитись найкращим рішенням, яке спростить ваш процес деплою.

Ми покроково розглянемо, як поєднати силу Django та Docker для безболісного деплою вашого проєкту. Не треба витрачати час на нудні пошуки гайдів — давайте разом розберемося, як зробити ваше деплоїнг-життя простішим і веселішим!

Щоб почати нашу пригоду з Docker, зверніться до офіційної документації Docker для його встановлення. Розглядати це тут ми не будемо.

Отже,

Підготовка Django до деплою

Virtual environment (не pyvenv єдиним!)

У своїй повсякденній практиці я використовую Poetry — прекрасна альтернатива pip.

Що ж це за звір такий та як він працює?

Poetry — це потужний інструмент для управління залежностями та віртуальним середовищем у вашому проєкті. Він дозволяє створювати ізольовані середовища для вашого застосунку, де можна встановлювати і оновлювати залежності без впливу на інші проєкти або систему в цілому.

Чому Poetry, а не звичайний pyvenv або pip? Він має багато переваг, включаючи зручний синтаксис для встановлення пакетів, автоматичне створення та оновлення файлу pyproject.toml, а також підтримку requirements.txt. Poetry робить управління залежностями зрозумілішим та зручнішим.

З Poetry все надзвичайно просто. По-перше, встановіть Poetry у вашій системі (детальні інструкції можна знайти на офіційному сайті Poetry). Потім, у папці вашого Django проєкту, виконайте команду poetry init, яка допоможе створити новий проєкт або використати існуючий. Виберіть свої налаштування та введіть необхідну інформацію про ваш проєкт.

Додайте необхідні залежності

Після того, як ми ознайомилися з Poetry та його простотою управління залежностями, перейдімо до кількох додаткових кроків, щоб наш Django проєкт був готовий до деплою.

Для запуску проєкту та деплою ми будемо використовувати gunicorn — потужний WSGI-сервер (Web Server Gateway Interface). Це забезпечить швидкий та надійний запуск нашого Django-застосунку.

poetry add gunicorn

Тепер, щоб наш Django-проєкт міг працювати з базою даних Postgresql, потрібно встановити залежність psycopg2:

poetry add psycopg2-binary

Після виконання цих команд в директорії вашого проєкту з’являться два файли:

  • pyproject.toml # - це основний файл конфігурації вашого проєкту, де містяться всі залежності та налаштування;
  • poetry.lock # - це файл із замкнутими версіями залежностей, які гарантують стабільність у вашому проєкті. Ви можете ігнорувати цей файл у системі контролю версій, оскільки Poetry буде автоматично виправляти його при встановленні залежностей.

Завдяки Poetry, ми зберегли наші залежності у віртуальному середовищі, що робить наш Django-проєкт незалежнішим та структурованішим.

Отже, virtual environment готовий до деплою

Сконфігуруйте settings.py та urls.py під «продакшн» та «local development»

Перш за все створіть .env файл для визначення змінних оточення (кредли БД, хости і так далі)

#.env
    DB_PORT=5432
    DB_HOST=projectname_postgres # ім'я хоcту в мережі Docker
    POSTGRES_DB=postgres
    POSTGRES_USER=postgres
    POSTGRES_PASSWORD=postgres

Далі треба створити файл local_settings.py та покласти його в теку ядра проєкту (там, де settings.py)

    # local_settings.py
    from .settings import * 
    # Додаємо '127.0.0.1' до ALLOWED_HOSTS, щоб Django міг обслуговувати запити з localhost
    ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
    # Використовуємо SQLite базу даних для локального розробки
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }

Додаємо дебаг-режим для локальної розробки

DEBUG = True

Редагуємо основний файл налаштувань `settings.py`:

    # settings.py
    import os
    ...

Вказуємо реальні хости доменів, які Django може обслуговувати на продакшн сервері

ALLOWED_HOSTS = ['example.com', 'www.example.com']

Використовуємо PostgreSQL базу даних для продакшн-сервера

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ.get('POSTGRES_DB'),
            'USER': os.environ.get('POSTGRES_USER'),
            'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
            'HOST': os.environ.get('DB_HOST'),  # або адреса вашого PostgreSQL сервера
            'PORT': os.environ.get('DB_POST'),  # порт (залиште порожнім, якщо використовуєте дефолтний порт)
        }
    }

Вимикаємо дебаг-режим для продакшн-сервера

DEBUG = False

Описуємо налаштування для статики та медіа

STATIC_ROOT = BASE_DIR / 'static'
    STATIC_URL = '/static/'
    MEDIA_ROOT = BASE_DIR / 'media'
    MEDIA_URL = '/media/'

Також потрібно доповнити корний файл `urls.py` для фетчінгу статики та медіа, якщо дебаг режим ввімкнений:

    # urls.py
    from django.contrib.staticfiles.views import serve
    from django.urls import path, include
    ...

    static_and_media_urls = [
        path('static/<path:path>', serve, {'document_root': settings.STATIC_ROOT}),
        path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
    ]
    urlpatterns = [...]
    urlpatterns + static_and_media_urls

Все! Ваш Django-проєкт готовий до деплою!

Готуємо Docker

Тепер, коли ми підготували наш Django-проєкт та налаштували віртуальне середовище за допомогою Poetry, настав час перейти до Docker. Ця чарівна технологія дозволить нам упакувати наш застосунок та всі його залежності у контейнери, що забезпечить легкий та безпечний деплой на будь-якому сервері або хмарній платформі.

Створимо дві директорії в корневій директорії проєкту:

backend
nginx

В директорії backend створюємо два файли: DockerFile та docker-entrypoint.sh

DockerFile:

  FROM python:3.10-buster # обираємо версію Python на якій буде працювати проєкт

    # описуємо віртуальне оточення:
    ENV PYTHONBUFFERED=1 \
        POETRY_VERSION=1.4.2 \
        POETRY_VIRTUALENVS_CREATE="false"
    
    RUN pip install "poetry==$POETRY_VERISON" # встановлюємо Poetry

Вказуємо робочу теку:

WORKDIR /app

копіюємо файли залежностей та баш-скрипт в корінь контейнера:

COPY pyproject.toml poetry.lock docker-entrypoint.sh ./

встановлюємо залежності

RUN poetry install --no-interaction --no-ansi --no-dev

копіюємо проєкт в робочу теку:

COPY project /app

вказуємо порт

EXPOSE 8000

даємо права на виконання ентріпоінту

RUN chmod +x docker-entrypoint.sh
    ENTRYPOINT ["./docker-entrypoint.sh"]

docker-entrypoint

  #!/bin/sh
    set -e
    until cd /app
    do
        echo "Wait for server volume..."
    done

робимо міграції перед запуском wsgi-сервера

 until python manage.py migrate
    do
        echo "Waiting for postgres ready..."
    done

збираємо статику

python manage.py collectstatic

та запускаємо wsgi сервер за допомогою gunicorn

gunicorn project.wsgi:application --bind 8000 --workers 4 --threads 4

project — це ім’я вашого проєкту.

Django ми вже остаточно підготували до деплою, тому тепер перейдемо до конфігурації контейнера.

NGINX

В теці nginx створюємо:

DockerFile

    FROM nginx:stable-alpine

    CMD ["nginx", "-g", "daemon off;"]

копіюємо файли ssl сертифікатів, попередньо замовивши-створивши їх для свого домену

 COPY nginx/ca.crt /etc/nginx/ssl/ca.crt
    COPY nginx/your-domain.com.crt /etc/nginx/ssl/your-domain.com.crt
    COPY nginx/your-domain.com.key /etc/nginx/ssl/your-domain.com.key

conf.d

> Файл конфігурації nginx (conf.d/default.conf)

   server {
        listen 80;
        server_name your-domain.com www.your-domain.com;
        rewrite ^ https://$server_name$request_uri? permanent;
    }
 
    server {
        listen 443 ssl;
        server_name your-domain.com www.your-domain.com;
        server_tokens off;
        ssl_certificate /etc/nginx/ssl/your-domain.com.crt;
        ssl_certificate_key /etc/nginx/ssl/your-domain.com.key;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        keepalive_timeout 70;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_stapling on;
        ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
        access_log /var/log/nginx/your-domain.com.access.log;
        error_log /var/log/nginx/your-domain.com.su.error.log;
    
    
        location /admin {
            try_files $uri @proxy_api;
        }

        location @proxy_api {
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header X-Url-Scheme $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass   http://project_django_1:8000;
        }

        location /static { alias /app/static; }

        location /media { alias /app/media; }
        
        location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_pass http://project_django_1:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Host $host;
        }
    }

Фух, так?...:)

Вже майже все!

Тепер нам залишилося створити найважливіший файл — docker-compose.yaml

Що ж це за файл такий і нащо він потрібен

docker-compose.yaml — це файл конфігурації для Docker Compose, інструмента, що дозволяє вам описувати та запускати багатоконтейнерні застосунки з легкістю.

У цьому файлі ви можете визначити усі контейнери, що складають ваш застосунок, а також налаштування для кожного контейнера. Це дозволяє вам встановити всі контейнери за одну команду та легко керувати їх взаємодією.

Чому він потрібен?

docker-compose.yaml забезпечує простий та стандартизований спосіб описувати ваш застосунок та його середовище. Завдяки цьому файлу, інші розробники можуть легко розгортати ваш застосунок на своїх комп’ютерах або серверах без необхідності докладного дослідження і налаштування.

Також docker-compose.yaml дозволяє зберігати всі налаштування проєкту в одному місці, що спрощує та зберігає наш процес деплою організованим та структурованим.

Створення docker-compose.yaml

Створімо docker-compose.yaml для нашого Django-проєкту. Відкрийте текстовий редактор та створіть новий файл з назвою docker-compose.yaml у кореневій папці вашого проєкту. Додайте такий зміст:

    version: '3'

    services:
      postgres:
        restart: unless-stopped
        image: postgres:13.1-alpine
        env_file:
          - ./.env
        volumes:
          - postgres_data:/var/lib/postgresql/data/
        networks:
          - project_network
    
      django:
        restart: unless-stopped
        build:
          context: .
          dockerfile: ./backend/DockerFile
        env_file:
          - ./.env
        volumes:
          - static_volume:/app/static
          - media_volume:/app/media
        networks:
          - project_network
        depends_on:
          - postgres

      nginx:
        restart: unless-stopped
        build:
          context: .
          dockerfile: ./nginx/DockerFile
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
          - static_volume:/app/static
          - media_volume:/app/media
        networks:
          - project_network
        depends_on:
          - django

    volumes:
      static_volume:
      media_volume:
      postgres_data:
    
    networks:
      project_network:
    driver: bridge

Опис сервісів

Опис кожного сервісу:

  • postgres: цей сервіс використовує образ PostgreSQL версії 13.1, з базовим образом Alpine Linux. Він має зазначені параметри restart: unless-stopped для автоматичного перезапуску сервісу, якщо він припинить роботу (крім явного зупинення). env_file вказує на файл .env, де містяться змінні оточення для контейнера. Використовується том postgres_data, щоб зберігати дані PostgreSQL між рестартами контейнера.
  • django: цей сервіс збирається з використанням DockerFile, що знаходиться в поточній директорії (context: .) і має назву DockerFile. Також використовує файл .env для змінних оточення. Використовує томи static_volume і media_volume для зберігання статичних та медіафайлів між рестартами контейнера. Залежить від сервісу postgres, щоб мати доступ до бази даних.
  • nginx: цей сервіс будується з використанням DockerFile, що знаходиться в папці ./nginx. Він перенаправляє порти 80 та 443 з контейнера на відповідні порти на локальній машині. Монтує файли конфігурації Nginx з локальної папки ./nginx/prod/conf.d. Використовує томи static_volume і media_volume для доступу до статичних і медіафайлів. Залежить від сервісу django, щоб мати доступ до вашого Django застосунку через Gunicorn.

Також, у файлі є визначені томи (volumes) і мережа (networks) для спільного використання даних та мережі між контейнерами.

Запуск за допомогою Docker Compose

Для запуску проєкту за допомогою Docker Compose відкрийте командний рядок у кореневій папці вашого проєкту та виконайте таку команду:

docker-compose up -d --build

Тепер ваш Django-застосунок та база даних PostgreSQL будуть запущені, і ваш проєкт буде доступний за адресою your-domain.com.

Готово!

Тепер у нас є повністю підготовлений Django-проєкт, готовий до деплою з використанням Docker та Docker Compose. Ви можете деплоїти його на будь-якому сервері або хмарній платформі з легкістю.

Дякую, що приєдналися до нашої пригоди у розробці web-застосунків з Django та Docker. Бажаю вам успіхів у вашому джедайському шляху!

P.S. Корисні команди Docker та Docker-compose

  • docker build: збирає Docker образ з DockerFile.

Приклад: docker build -t my_image_name:latest

  • docker run: запускає контейнер із вибраним образом.

Приклад:

docker run -d -p 8000:80 my_image_name:latest
  • docker ps: показує список активних контейнерів.

Приклад:

docker ps
  • docker stop: зупиняє активний контейнер.

Приклад:

docker stop container_id
  • docker rm: видаляє зупинений контейнер.

Приклад:

docker rm container_id
  • docker images: показує список доступних Docker образів.

Приклад:

docker images
  • docker rmi: Видаляє Docker образ.

Приклад:

docker rmi image_id
  • docker exec: виконує команду всередині контейнера.

Приклад:

docker exec -it container_id command
  • docker logs: Переглядає логи контейнера.

Приклад:

docker logs container_id

Команди Docker Compose:

  • docker-compose up: піднімає всі контейнери зі складу docker-compose.yaml.

Приклад:

docker-compose up -d (запуск у фоновому режимі)
  • docker-compose down: зупиняє та видаляє всі контейнери, створені за допомогою docker-compose up.

Приклад:

docker-compose down
  • docker-compose ps: показує статус контейнерів, визначених у docker-compose.yaml.

Приклад:

docker-compose ps
  • docker-compose logs: переглядає логи всіх контейнерів з docker-compose.yaml.

Приклад:

docker-compose logs
  • docker-compose exec: виконує команду всередині контейнера, визначеного в docker-compose.yaml.

Приклад:

docker-compose exec service_name command
  • docker-compose build: збирає образи для всіх контейнерів, визначених у docker-compose.yaml.

Приклад:

docker-compose build
  • docker-compose rm: видаляє зупинені контейнери, визначені у docker-compose.yaml.

Приклад:

docker-compose rm
👍ПодобаєтьсяСподобалось7
До обраногоВ обраному11
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

хотів би оцінки моєї статті на цюж тему кому не важко blog.dsoft.com.ua/...​0/zapusk-django-v-docker

Адмінка та сваггер працюють при деплоі? Бо в мене чомусь при сваггер нормально,а статіка для адмінки не грузиться(при інших налаштуваннях)

Так, працюють

Дякую, нормас стаття

Статтю не читав повністю, але краєм ока зачепило кілька моментів. Таке враження, що автор сам новачок з docker.
Я так і не зрозумів навіщо тягнути buster у проєкт, який мабуть важить під гігабайт, та містить безліч вразливостей, коли існує alpine. Buster може бути корисним, тільки коли потрібна повноцінна система, наприклад, для деяких задач при multi-stage білду.
Також не розумію навіщо автор взагалі білдить новий імедж nginx у данному контексті. Конфіг же прокинув через mount, а сертифікати чомусь ні.
Ще автору треба звернути увагу на індентацію, особливо у yaml. А також бути консистентним з назвами сервісів, як ось наприклад, project_django_1 стає просто django у compose (DNS Docker’у резолвить тільки імена сервісів та container_name, але що одне, що інше відрізняється від того, що в конфігах nginx).
Ну і тема локального розгортання не розкрита зовсім.

Дякую за коментар.
Ні, я не зовсім новачок в докер, але де я buster тягну? Якщо і nginx та postgres alpine.
Чи ви за django? Навіть при такому розкладі розмір контейнеру не перевищує 100мб.
Щодо інтентації — ок, виправлю, статтю віддавав в маркдауні, тому є косяки
Щодо консистенції назв — дякую, теж виправлю
Щодо теми локального розгортання — згоден.

Нажаль ні, buster буде 300мб+ для завантаження, та десь під гігабайт на диску (в порівнянні з alpine ± 45мб та 100мб відповідно). Але розмір то таке, не всім важливо, а ось безпека має значення. Щойно глянув dockerhub, buster має сотні відомих вразливостей, і одну критичну, у той же час alpine не має ні одної взагалі.

Дякую, візьму до уваги як в практиці так і в процесі виправлення статі

пару питань:
— що треба, щоб змінити «реальний хост» машини на якийсь інший?
— де зламається, якщо поміняти ім’я контейнера django -> api_service? в статті побачив використання доменів localhost, www.example.com, project_django_1, your-domain.com, projectname_postgres в різних місцях своє
— де зламається, якщо все це помістити в папці myapp? Тобто `mkdir myapp`, покласти туди всі компоузи і код, та запустити docker-compose up
— чи можна змінити статику, вбити docker-compose up і вважати, що працює? p.s. там десь —no-input параметра наче не вистачає
— скільки дій треба, щоб запустити не в DEBUG режимі?
— як створити superuser’а для джанго адмінки в цій конфігурації?

коротно, що не ок на перший погляд:
— «копіюємо файли ssl сертифікатів, попередньо замовивши-створивши їх для свого домену» рівноцінне до «намалюйте сову» для новачка
— копіювати сертифікати «замовлених» сертифікатів в образ дуже поганий приклад, як і будь які секрети
— кожна зміна в коді буде перезбирати образ
— задавати віддачу статики через джангу не приховавши за DEBUG режимом погана ідея. Колись буде в продакшені джанга віддавати статику, замість nginx із-за недоконфігурації і дізнаєтесь вже на користувачах
— використовується повний buster образ (300мб+), замість slim (<100mb) версії. постгрес і nginx хоча б на alpine
— купа хардкода, який ломає весь деплой при малих змінах і змушує дебажити всі ці конфіги, шукати де зламалось
— нема прикладів конфігів/скриптів/файлів зібраних повністю разом, щоб скопіював і затестив. Копіювати альт-табом по кожній строчці зі статті новачку буде не дуже зручно
— форматування прикладів попахує пофігізмом. десь є таби, десь нема, десь вони врізнобій
— якщо це приклад для локального деплою, то генерувати новий образ на кожен чих повинно набриднути дуже швидко. щоб такого не було, краще код монтувати, а не копіювати. Якщо для продакшену, то краще не треба.

Дякую за зауваження. Це мій перший досвід написання статті, і дякую редакторам що запропонували до публікації.
Щодо ваших зауважень — в цілому, так, це чисто суб’єктивне викладання матеріалу, і тому є деякі «баги» і факапи.
Наприклад, щодо сертифікатів — так, це небезпечно, але розраховане на людей які не шарять в letsencrypt та інше.
Щодо форматування тут взагалі біль та пофігізм саме редакторів. Перед публікацією я дуууууже багато після них виправив, але видно недостатньо

Щодо запуску локально, щоб на кожний чих не генерувати новий образ — або прокинути порт для Постгресу, або просто використовувати підхід локальних сеттінгів.

Щодо повного образу — згоден.

Віддача статики більше для дебаг режиму аніж проду. Конфігу nginx достатньо для хендлінгу статики. Але, дякую за зауваження.

Щодо «як створити суперюзера і скільки дій знадобиться» — цей момент я тупо пропустив:) достатньо однієї команди docker exec -it container_name command
Ім’я контейнера — це теж моя бажина.

Підсумую, що зважаючи на мою недосвідченість в написанні статей (але аж ніяк не кодінгу та роботи) до ваших коментів я прислухаюсь і найближчим часом виправлю статтю (дякую редакторам, що можливість редагування навіть після публікації залишилася)

Ще раз дякую за такий конструктивізм та деьальність моїх промахів

Щодо форматування тут взагалі біль та пофігізм саме редакторів.

Ви можете відкрити статті де вже є гарне форматування блоків коду, відкрити код сторінки й подивитись як треба

Ще раз повторюсь — це перший досвід публікацій. Дякую за поради

Досвід написання статей другорядний бо більше значення мають людські відносини

Якщо ви звинувачуєте редакторів у байдужості то будьте готові що редактори дійсно будуть байдужі до ваших статей

Що казати, якщо в першій ітерації превью вони написали що я Павло, а не Дмитро.
Тоді якщо я «звинувачую» то чи можу я казати що критичні коментарі це теж звинувачення?
Все відносно.

Ви з Полтави тому ймовірно й назвали випадково Павлом

Я якось назвав рекрутера Тарасом бо його прізвище було Теплий

Вашу думку зрозумів

Автор сам собі ботів нагнав в перших двох коментарях? Лооол.
Аккаунти з 0 інфо і 1 коментарем.

Ну майже боти)
Просто підтримка від знайомих

Це теж треба мати вміння переконати знайомих написати змістовний коментар про технічну статтю

ну в тебе вміння акцентувати на тому що «фахівець з досвідом» теж добре розвинуте)

За 3 місяці я використовував слово «досвід» у 18 коментарях, але тільки один раз у контексті «фахівець з досвідом»

На превеликий жаль CORS policy DOU не дозволяють фетчити дані. (я про doutivity)

В браузері Firefox (версії 116.0) та Chrome (версії 114.0.0.0) працює

Дивлюсь запит у вкладці «Network» то бачу відповідь від сервера:

access-control-allow-origin: *
access-control-allow-methods: GET,POST,OPTIONS

Якщо в мене перестане працювати то буду розбиратись

Гарна покрокова стаття. Для новачків саме то. Ніде не бачив таких гайдів

Мені, фахівцю з досвідом, було складно читати цю статтю

Чому? Все детально i доступно описано для розробникiв будь-якого рiвню досвiду.

Херовый у тебя опыт какой-то

Вас хтось вже образив до мене, що так реагуєте

копія в web.archive.org

шановний, ви хочете тут показати що ви краще за інших? я не розумію — критикуєте — критикуйте конструктивно, будь ласка.

В статті дуже багато прогалин тому очікую що хтось ще покритикує, а я вже покритикую після вихідних якщо щось ще залишиться

Дякую.
Можливо є прогалини, але для базового деплою Джанго проекту цього достатньо. Більш глибше не варто просто

Та ні, Ваш коментар звучить як невідомо що. «Мені стаття не сподобалась, бо ....» - то є варте уваги. А так то як перднути в калюжу

Мій коментар написаний чітко:

Мені, фахівцю з досвідом, було складно читати цю статтю

Ви хвалебний коментар підтримали тому у вас проблеми зі сприйняттям критики, а також проблеми зі спілкуванням

Не знаю, як на мене гарна стаття для того, щоб зрозуміти що, де і для чого використовується

Дякую! Завдяки цій статті навчився працювати з докером ))

Чудова стаття про деплой Django!
Це неймовірно корисний маткріал для новачків, дякую за статтю!

Підписатись на коментарі