Compare commits
12 Commits
0aa3ce9f47
...
v3.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b458b6723 | |||
| 72b0a4145b | |||
| cd8921c459 | |||
| 5d7470ac7d | |||
| cb28acfaf2 | |||
| 5ac33c2a5d | |||
| 2eb6636ad1 | |||
| 11294a6c8c | |||
| 19bf4fb293 | |||
| 75302a563a | |||
| 57d3f1ea2c | |||
| 4669f53b69 |
46
.dockerignore
Normal file
46
.dockerignore
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Исключаем мусор и локальные артефакты, чтобы Docker-контекст был компактным.
|
||||||
|
|
||||||
|
# Git и IDE-файлы в образ не нужны.
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Секреты и локальные настройки не должны попадать в контейнерный контекст.
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.env.sample
|
||||||
|
|
||||||
|
# Виртуальное окружение и служебные артефакты Python.
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.log
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.coverage*
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
|
||||||
|
# Локальные базы и дампы SQLite в контейнер не тащим.
|
||||||
|
*.sqlite3
|
||||||
|
database/
|
||||||
|
media/
|
||||||
|
|
||||||
|
# Локальная сборка фронтенда пока не нужна в Docker-контексте.
|
||||||
|
# Если позже соберём frontend внутри Docker, это правило можно пересмотреть.
|
||||||
|
frontend-assembly/
|
||||||
|
|
||||||
|
# Загруженные медиа-файлы монтируются отдельно и не должны раздувать контекст.
|
||||||
|
public/media/
|
||||||
|
|
||||||
|
# Документация и служебные git-ignore-файлы не нужны в runtime-образе.
|
||||||
|
*.md
|
||||||
|
**/.gitignore
|
||||||
|
|
||||||
|
# Репозиторные и оркестрационные файлы не нужны внутри runtime-образа.
|
||||||
|
.gitea/
|
||||||
|
Dockerfile
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
13
.env.sample
13
.env.sample
@@ -20,3 +20,16 @@ DJANGO_EMAIL_FROM=you@email.com
|
|||||||
# URL для доступа к админке Django (можно сменить для безопасности, чтобы боты не могли её найти)
|
# URL для доступа к админке Django (можно сменить для безопасности, чтобы боты не могли её найти)
|
||||||
ADMIN_URL=admin/
|
ADMIN_URL=admin/
|
||||||
|
|
||||||
|
# Системные пути на хосте (ТОЛЬКО ДЛЯ ПРОДАКШЕНА)
|
||||||
|
# Используется скриптом для генерации корректного Nginx конфига (alias к медиа-файлам).
|
||||||
|
# На локальной машине разработчика (Dev) эта переменная игнорируется.
|
||||||
|
# В ПРОДАКШЕНЕ: Укажите полный путь к папке проекта на сервере
|
||||||
|
HOST_PROJECT_PATH=/home/username/projects
|
||||||
|
|
||||||
|
# Настройки доступа к пакетам в репозитории, чтобы wathtower мог проверять их свежесть и скачивать для обновления.
|
||||||
|
# Получить эти данные можно в настройках вашего репозитория, например:
|
||||||
|
# для GitHub: в разделе "Developer settings" -> "Personal access tokens";
|
||||||
|
# для Gitea: в разделе "Settings / Настройки" -> "Actions / Действия" -> "Secrets / Секреты".
|
||||||
|
REPO_USER=[login]
|
||||||
|
REPO_PASS=[token]
|
||||||
|
|
||||||
|
|||||||
72
.gitea/workflows/docker-publish.yaml
Normal file
72
.gitea/workflows/docker-publish.yaml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
run-name: Build and Push Docker Image ${{ github.ref_name }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Запускать сборку только при создании тега, начинающегося с 'v' (например, v1.0.0, v2.3.1)
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.cube2.ru
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest # Или метка вашего раннера, если он специфичный (например, macos или self-hosted)
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Настройка QEMU для мультиплатформенной сборки (если нужно собирать под разные архитектуры)
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
# Настройка Docker Buildx (обязательно для build-push-action)
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
# Логин в реестр Gitea
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
# Извлечение метаданных (тегов и лейблов) для Docker
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=tag
|
||||||
|
type=raw,value=latest,enable=${{ github.ref_type == 'tag' }}
|
||||||
|
|
||||||
|
# Сборка и отправка образа
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
# Собираем под текущую архитектуру (linux/amd64).
|
||||||
|
# Если сервер и MacMini на разных архитектурах (x86 vs ARM), добавьте нужные, например: linux/amd64,linux/arm64
|
||||||
|
# platforms: linux/amd64,linux/arm64
|
||||||
|
# ---
|
||||||
|
# Собираем только под linux/amd64 (для скорости)
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
# ДОБАВЛЕНО для медленного интернета и оптимизации сборки:
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
timeout: 1800 # Увеличено до 30 минут на всю сборку
|
||||||
90
Dockerfile
Normal file
90
Dockerfile
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# =================================================
|
||||||
|
# STAGE 1: Builder - Установка зависимостей
|
||||||
|
# =================================================
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
|
||||||
|
# Устанавливаем переменные окружения
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PIP_DEFAULT_TIMEOUT=100
|
||||||
|
|
||||||
|
# Устанавливаем Poetry
|
||||||
|
RUN pip install --no-cache-dir --default-timeout=100 --retries 10 poetry poetry-plugin-export
|
||||||
|
|
||||||
|
# Создаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем только файлы зависимостей для кэширования этого слоя
|
||||||
|
COPY pyproject.toml poetry.lock /app/
|
||||||
|
|
||||||
|
# Экспортируем lock-файл в requirements.txt и ставим зависимости через pip.
|
||||||
|
# Это обычно быстрее и проще для Docker, чем полноценная установка через Poetry.
|
||||||
|
RUN poetry export --format requirements.txt --without-hashes --with dev --output /tmp/requirements.txt \
|
||||||
|
&& pip install --no-cache-dir --default-timeout=100 --retries 10 -r /tmp/requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
# =================================================
|
||||||
|
# STAGE 2: Final - Создание чистого и безопасного образа
|
||||||
|
# =================================================
|
||||||
|
FROM python:3.12-slim AS stage-final
|
||||||
|
|
||||||
|
# Устанавливаем переменные окружения
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV DJANGO_SETTINGS_MODULE=cadpoint.settings
|
||||||
|
|
||||||
|
|
||||||
|
# Создаем пользователя без прав root для безопасности
|
||||||
|
# RUN addgroup --system app && adduser --system --ingroup app app
|
||||||
|
|
||||||
|
# Создаем рабочую директорию
|
||||||
|
WORKDIR /home/app/web
|
||||||
|
|
||||||
|
# Копируем установленные Python-пакеты из builder-стадии
|
||||||
|
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
|
|
||||||
|
# Копируем исходный код проекта и устанавливаем правильного владельца
|
||||||
|
# ИЗМЕНЕНИЕ: app:app -> 1000:1000
|
||||||
|
COPY --chown=1000:1000 . .
|
||||||
|
|
||||||
|
# Создаём директорию для конфигов nginx и даём права пользователю app
|
||||||
|
# Это выполняется ещё от root, поэтому проблем с permissions не будет.
|
||||||
|
RUN mkdir -p /nginx_configs_host/nginx && chown -R 1000:1000 /nginx_configs_host
|
||||||
|
|
||||||
|
# Создаём директорию для собранной статики и даём права пользователю app.
|
||||||
|
# `STATIC_ROOT` в settings.py живёт внутри `public`.
|
||||||
|
RUN mkdir -p /home/app/web/public/staticfiles && chown -R 1000:1000 /home/app/web/public
|
||||||
|
|
||||||
|
# Создаём директорию для ошибок (404, 500) и даём права пользователю app
|
||||||
|
RUN mkdir -p /home/app/web/public/media/_error && chown -R 1000:1000 /home/app/web/public/media
|
||||||
|
|
||||||
|
# Создаём директорию для БД и даём права пользователю app
|
||||||
|
# Это важно когда БД монтируется как том с хоста
|
||||||
|
RUN mkdir -p /home/app/web/database && chown -R 1000:1000 /home/app/web/database
|
||||||
|
|
||||||
|
# Переключаемся на пользователя без прав root
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
|
||||||
|
# Собираем статику
|
||||||
|
# Используем dummy ключ, так как .env файла нет на этапе сборки
|
||||||
|
RUN SECRET_KEY=dummy python cadpoint/manage.py collectstatic --noinput --clear
|
||||||
|
|
||||||
|
# Открываем порт
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Проверка здоровья контейнера
|
||||||
|
# Docker будет периодически проверять, жив ли контейнер, отправляя GET запрос к главной странице.
|
||||||
|
# Параметры:
|
||||||
|
# --interval=30s - проверка каждые 30 секунд
|
||||||
|
# --timeout=3s - ожидаем ответ максимум 3 секунды
|
||||||
|
# --start-period=10s - даем контейнеру 10 секунд на запуск перед первой проверкой
|
||||||
|
# --retries=3 - объявляем контейнер unhealthy после 3 неудачных попыток
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()" || exit 1
|
||||||
|
|
||||||
|
# Переходим в директорию с manage.py для корректного запуска gunicorn
|
||||||
|
WORKDIR /home/app/web/cadpoint
|
||||||
|
|
||||||
|
# Команда запуска (два воркера для лучшей производительности, можно увеличить до число ядер на хосте)
|
||||||
|
CMD ["python", "-m", "gunicorn", "--workers", "2", "--bind", "0.0.0.0:8000", "cadpoint.wsgi:application"]
|
||||||
@@ -57,8 +57,6 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.sitemaps',
|
'django.contrib.sitemaps',
|
||||||
# Панель отладки показываем только в dev-окружении при `DEBUG=True`.
|
|
||||||
'debug_toolbar',
|
|
||||||
'django_select2',
|
'django_select2',
|
||||||
'easy_thumbnails',
|
'easy_thumbnails',
|
||||||
'filer.apps.FilerConfig',
|
'filer.apps.FilerConfig',
|
||||||
@@ -70,8 +68,6 @@ INSTALLED_APPS = [
|
|||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
# Middleware нужен, иначе панель debug toolbar просто не влезет в response.
|
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
@@ -233,3 +229,33 @@ NUM_NAV_ITEMS_IN_PAGE = 7
|
|||||||
# Число статей (заголовок + тизер) на странице
|
# Число статей (заголовок + тизер) на странице
|
||||||
NUM_ITEMS_IN_PAGE = NUM_NAV_ITEMS_IN_PAGE
|
NUM_ITEMS_IN_PAGE = NUM_NAV_ITEMS_IN_PAGE
|
||||||
|
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
# В деве оставляем стандартную отдачу статики Django без WhiteNoise.
|
||||||
|
STORAGES = {
|
||||||
|
'staticfiles': {
|
||||||
|
'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
# Django Debug Toolbar нужен только в dev
|
||||||
|
def _show_debug_toolbar(request):
|
||||||
|
"""Скрывает debug toolbar внутри админки Django"""
|
||||||
|
return not request.path.startswith(f'/{ADMIN_URL}')
|
||||||
|
|
||||||
|
INSTALLED_APPS.append('debug_toolbar')
|
||||||
|
MIDDLEWARE.insert(1, 'debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
'SHOW_TOOLBAR_CALLBACK': _show_debug_toolbar,
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
# В проде WhiteNoise обслуживает собранную статику и файлы из `public`.
|
||||||
|
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
|
||||||
|
STORAGES = {
|
||||||
|
'staticfiles': {
|
||||||
|
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
# Конфигурация WhiteNoise для обслуживания статических файлов и файлов из /public (например,
|
||||||
|
# robots.txt, favicon.ico и т.п.)
|
||||||
|
WHITENOISE_ROOT = PUBLIC_DIR
|
||||||
|
|||||||
@@ -62,7 +62,43 @@ handler403 = 'web.views.handler403'
|
|||||||
handler500 = 'web.views.handler500'
|
handler500 = 'web.views.handler500'
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
import mimetypes
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
|
from django.views.static import serve
|
||||||
|
|
||||||
|
def _serve_public_root_file(request, path):
|
||||||
|
"""Отдаёт файлы из корня `public` в dev-режиме в utf-8."""
|
||||||
|
response = serve(request, path, document_root=settings.PUBLIC_DIR)
|
||||||
|
content_type, _ = mimetypes.guess_type(path)
|
||||||
|
if content_type:
|
||||||
|
if content_type.startswith('text/'):
|
||||||
|
response['Content-Type'] = f'{content_type}; charset=utf-8'
|
||||||
|
else:
|
||||||
|
response['Content-Type'] = content_type
|
||||||
|
elif path.endswith('.txt'):
|
||||||
|
response['Content-Type'] = 'text/plain; charset=utf-8'
|
||||||
|
elif path.endswith('.html'):
|
||||||
|
response['Content-Type'] = 'text/html; charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _iter_public_root_files():
|
||||||
|
"""Находит все обычные файлы в корне `public`, кроме служебных артефактов."""
|
||||||
|
for file_path in sorted(settings.PUBLIC_DIR.iterdir()):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
if file_path.name.startswith('.'):
|
||||||
|
continue
|
||||||
|
if file_path.name == 'README.md':
|
||||||
|
continue
|
||||||
|
yield file_path.name
|
||||||
|
|
||||||
|
PUBLIC_ROOT_URLPATTERNS = [
|
||||||
|
path(filename, _serve_public_root_file, {'path': filename})
|
||||||
|
for filename in _iter_public_root_files()
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns = [path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
|
urlpatterns = [path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns
|
||||||
|
urlpatterns = [*PUBLIC_ROOT_URLPATTERNS, *urlpatterns]
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.PUBLIC_DIR.joinpath('static'))
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
# urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
# urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|||||||
@@ -14,15 +14,15 @@
|
|||||||
<meta name="generator" content="handcraft" />
|
<meta name="generator" content="handcraft" />
|
||||||
<title>CADpoint.ru - http 500 error</title>
|
<title>CADpoint.ru - http 500 error</title>
|
||||||
<meta name="theme-color" content="#F5F5F5" />
|
<meta name="theme-color" content="#F5F5F5" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/svgs/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/svgs/favicon.svg" />
|
||||||
<link rel="icon" type="image/png" href="/static/img/favicon.png" />
|
<link rel="icon" type="image/png" href="/media/_error/img/favicon.png" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" type="image/x-icon" href="/media/_error/img/favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
<div style="width:100%;height:80%;display:table;position:absolute;top:0;left:0;">
|
||||||
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
<div style="display:table-cell;text-align:center;vertical-align:middle;">
|
||||||
<div style="display:inline-block;">
|
<div style="display:inline-block;">
|
||||||
<img src="/static/svgs/cappoint_under_reconstruction.svg"
|
<img src="/media/_error/svgs/cappoint_under_reconstruction.svg"
|
||||||
alt="cadpoint.ru - under reconstruction" style="width: 80vw;"/>
|
alt="cadpoint.ru - under reconstruction" style="width: 80vw;"/>
|
||||||
<pre>сайт cadpoint.ru реконструируется... подождите, скоро все вся станет не так как прежде...</pre>
|
<pre>сайт cadpoint.ru реконструируется... подождите, скоро все вся станет не так как прежде...</pre>
|
||||||
</div>
|
</div>
|
||||||
147
config/nginx/cadpoint-app--external-nginx.conf
Normal file
147
config/nginx/cadpoint-app--external-nginx.conf
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# config/nginx/cadpoint-app--external-nginx.conf
|
||||||
|
# ==============================================================================
|
||||||
|
# ЭТАЛОННЫЙ КОНФИГУРАЦИОННЫЙ ФАЙЛ NGINX (Reverse Proxy для Docker)
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# ВНИМАНИЕ:
|
||||||
|
# Этот файл является шаблоном. При первом деплое он копируется в `/home/user/app/cadpoint-site/config/nginx/cadpoint-app--external-nginx.conf`,
|
||||||
|
# а затем (уже руками) через силинк в `/etc/nginx/sites-available/` и активируется.
|
||||||
|
# При последующих деплоях он НЕ ПЕРЕЗАПИСЫВАЕТСЯ автоматически, чтобы не затереть SSL-сертификаты и ручные правки.
|
||||||
|
#
|
||||||
|
# Если вы изменили этот файл в репозитории и хотите применить изменения на проде:
|
||||||
|
# вам нужно обновить файл в `/home/user/app/cadpoint-site/config/nginx/cadpoint-app--external-nginx.conf` вручную (diff + copy).
|
||||||
|
#
|
||||||
|
# Так же (рядом) будет создан образец этого файла `nginx_cadpoint.conf.example`, который будет обновляться при деплоях
|
||||||
|
# из репозитория, чтобы вы могли видеть, что изменилось и при необходимости перенести эти изменения на прод.
|
||||||
|
#
|
||||||
|
# Предполагаемая структура на сервере:
|
||||||
|
# /home/user/app/cadpoint-site/
|
||||||
|
# ├── docker-compose.yml
|
||||||
|
# ├── .env
|
||||||
|
# ├── media/ <-- Сюда Nginx смотрит напрямую (Docker volume)
|
||||||
|
# └── ...
|
||||||
|
|
||||||
|
# 1. Описываем, где живет наш Django в Docker
|
||||||
|
upstream cadpoint-django {
|
||||||
|
# Мы пробрасываем порт 8050 из контейнера наружу (в docker-compose.yml имя сервиса 'web', контейнер 'cadpoint-backend')
|
||||||
|
server 127.0.0.1:8050;
|
||||||
|
keepalive_requests 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Конфигурируем сервер
|
||||||
|
server {
|
||||||
|
server_name test.cadpoint.ru; # Основное доменное имя
|
||||||
|
|
||||||
|
# Слушаем 80 порт (Certbot потом добавит сюда редирект на 443 и настройки SSL)
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 10M; # Разрешаем загрузку не слишком больших картинок
|
||||||
|
|
||||||
|
# Логи (пути могут отличаться в зависимости от настроек сервера, здесь стандартные для Ubuntu)
|
||||||
|
access_log /var/log/nginx/cadpoint.access.log;
|
||||||
|
error_log /var/log/nginx/cadpoint.error.log;
|
||||||
|
|
||||||
|
# --- GZIP (Сжатие) ---
|
||||||
|
# Очень важно для динамического HTML от Django, который Gunicorn отдает несжатым.
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on; # Добавляет заголовок Vary: Accept-Encoding
|
||||||
|
gzip_proxied any; # Сжимать ответы, даже если мы за прокси
|
||||||
|
gzip_comp_level 6; # Оптимальный баланс скорость/сжатие
|
||||||
|
gzip_min_length 1000; # Не сжимать совсем мелочь
|
||||||
|
# Типы файлов для сжатия (HTML сжимается автоматически, его писать не нужно)
|
||||||
|
gzip_types
|
||||||
|
text/plain
|
||||||
|
text/css
|
||||||
|
text/xml
|
||||||
|
text/javascript
|
||||||
|
application/javascript
|
||||||
|
application/json
|
||||||
|
application/xml
|
||||||
|
application/xml+rss
|
||||||
|
image/svg+xml
|
||||||
|
image/x-icon
|
||||||
|
application/vnd.ms-fontobject
|
||||||
|
font/woff
|
||||||
|
font/woff2;
|
||||||
|
|
||||||
|
# --- МЕДИА ФАЙЛЫ (Загруженный контент) ---
|
||||||
|
# Nginx отдает их напрямую с диска хоста, не дергая Docker.
|
||||||
|
# Путь должен совпадать с тем, где лежит volume на хост-машине.
|
||||||
|
# ВАЖНО: Убедитесь, что пользователь nginx (www-data) имеет права на чтение этой папки!
|
||||||
|
# ТРЕБУЕТСЯ ЗАМЕНА ПРИ ДЕПЛОЕ: /home/user/app/cadpoint-site -> ваш реальный путь
|
||||||
|
location /media/ {
|
||||||
|
alias /home/user/app/cadpoint-site/media/;
|
||||||
|
expires 30d; # Кешируем картинки на месяц
|
||||||
|
add_header Cache-Control "public, no-transform";
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- СТРАНИЦЫ ОШИБОК (Custom Error Pages) ---
|
||||||
|
# Если Django упал (502) или сработал тайм-аут (504), Nginx должен отдать статический HTML.
|
||||||
|
# Эти файлы должны лежать в папке, доступной Nginx (например, в `media/_error`).
|
||||||
|
#
|
||||||
|
# ВАЖНО:
|
||||||
|
# 1. Файлы 50x.html (500, 502, 503, 504) копируются в `media/_error` при старте контейнера (см. docker-compose.prod.yml -> command).
|
||||||
|
# 2. error_page директива перехватывает ошибки от апстрима (Gunicorn).
|
||||||
|
error_page 500 /500.html;
|
||||||
|
error_page 502 /502.html;
|
||||||
|
error_page 503 /503.html;
|
||||||
|
error_page 504 /504.html;
|
||||||
|
|
||||||
|
location = /500.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /502.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /503.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /504.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
|
||||||
|
# 404 (и другие) тоже нужно кастомизировать... обычно Django сам отдает 404.
|
||||||
|
# Но, например, Nginx отдаст 404 при ошике доступа к media-файлам (они храняться на хосте, а не в контейнере)
|
||||||
|
error_page 400 /400.html;
|
||||||
|
error_page 401 /401.html;
|
||||||
|
error_page 403 /403.html;
|
||||||
|
error_page 404 /404.html;
|
||||||
|
error_page 413 /413.html;
|
||||||
|
error_page 429 /429.html;
|
||||||
|
|
||||||
|
location = /400.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /401.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /403.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /404.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /413.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
location = /429.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
|
||||||
|
# Указываем единую страницу (на реконструкции) для всех прочих ошибок
|
||||||
|
error_page 405 406 407 408 409 410 411 412 414 415 416 417 418 421 422 423 424 425 426 428 431 451 /under_reconstruction.html;
|
||||||
|
location = /under_reconstruction.html { root /home/user/app/cadpoint-site/media/_error; internal; }
|
||||||
|
|
||||||
|
# --- ВСЁ ОСТАЛЬНОЕ (Django + WhiteNoise) ---
|
||||||
|
# Статика (/static/), robots.txt, favicon.ico и сам сайт обрабатываются внутри контейнера.
|
||||||
|
# Nginx просто прокидывает запрос внутрь.
|
||||||
|
location / {
|
||||||
|
proxy_pass http://cadpoint-django;
|
||||||
|
|
||||||
|
# Передаем правильные заголовки, чтобы Django знал реальный IP и протокол
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Если нужно чтобы Django обрабатывал и HTTP, и HTTPS, то можно раскомментировать эту строку
|
||||||
|
# и передавать реальный протокол от клиента
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Явно указываем https, потому что клиент всегда приходит по HTTPS к Nginx
|
||||||
|
# Даже если внутри контейнера это HTTP на 127.0.0.1:8050, для Django это должно быть HTTPS
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
|
||||||
|
# Тайм-ауты (важно для долгих операций, если они есть)
|
||||||
|
proxy_read_timeout 180s;
|
||||||
|
proxy_connect_timeout 180s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Редирект с www на без-www (SEO best practice)
|
||||||
|
# server {
|
||||||
|
# server_name www.cadpoint.ru;
|
||||||
|
# listen 80;
|
||||||
|
# return 301 $scheme://cadpoint.ru$request_uri; # Всегда редиректим на основной домен
|
||||||
|
# }
|
||||||
65
docker-compose.local.yml
Normal file
65
docker-compose.local.yml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Docker Compose для РАЗРАБОТКИ (Local Development)
|
||||||
|
# Этот файл содержит настройки для локальной работы (live reload, debug).
|
||||||
|
# Запуск: docker compose -f docker-compose.local up --build
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
# Имя контейнера для удобства
|
||||||
|
container_name: cadpoint-backend-dev
|
||||||
|
|
||||||
|
# Сборка из текущей директории
|
||||||
|
build: .
|
||||||
|
|
||||||
|
# Проброс портов (чтобы сайт был доступен на localhost:8055)
|
||||||
|
ports:
|
||||||
|
- "8055:8000"
|
||||||
|
|
||||||
|
# 1. КОМАНДА ЗАПУСКА (Dev режим)
|
||||||
|
# Используем --reload для авто-перезагрузки при изменении кода.
|
||||||
|
# Уменьшаем число воркеров до 1 (ресурсы dev-машины можно не экономит, но одного достаточно).
|
||||||
|
# Убираем collectstatic (в dev Django сам может отдавать статику или она нам не так важна сжатой)
|
||||||
|
# Но миграции оставляем, чтобы база была актуальной.
|
||||||
|
command: >
|
||||||
|
sh -c "python manage.py migrate --noinput &&
|
||||||
|
gunicorn --workers 1 --bind 0.0.0.0:8000 --reload cadpoint.wsgi:application"
|
||||||
|
|
||||||
|
# 2. МОНТИРОВАНИЕ КОДА (Live Reload)
|
||||||
|
# Подключаем локальные папки внутрь контейнера, чтобы Gunicorn видел изменения без пересборки образа.
|
||||||
|
volumes:
|
||||||
|
# Монтируем основной код проекта.
|
||||||
|
# Так как web, templates и manage.py лежат внутри cadpoint/, одного этого маунта достаточно.
|
||||||
|
- ./cadpoint:/home/app/web/cadpoint
|
||||||
|
|
||||||
|
# Монтируем всю папку public (Static + Media)
|
||||||
|
# Это нужно, чтобы:
|
||||||
|
# 1. Изменения в CSS/JS (public/static) сразу были видны (Live Reload).
|
||||||
|
# 2. Загруженные картинки (public/media) сохранялись на диске.
|
||||||
|
- ./public:/home/app/web/public
|
||||||
|
|
||||||
|
# Монтируем базу данных (чтобы данные сохранялись при пересоздании контейнера)
|
||||||
|
# Используем ту же папку database, что и на проде, для единообразия.
|
||||||
|
# ВАЖНО: Django ищет базу в BASE_DIR.parent / 'database/db.sqlite3'
|
||||||
|
# В контейнере BASE_DIR=/home/app/web/cadpoint, значит путь к базе: /home/app/web/database/db.sqlite3
|
||||||
|
- ./database:/home/app/web/database
|
||||||
|
|
||||||
|
# 3. ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ
|
||||||
|
env_file:
|
||||||
|
# файл с переменными окружения для разработки
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
# на всякий случай, принудительно включаем DEBUG и DEBUG-уровень логов (вдруг в .env что-то не так)
|
||||||
|
- DEBUG=True
|
||||||
|
- DJANGO_LOG_LEVEL=DEBUG
|
||||||
|
# В dev нам не нужно ограничивать буферизацию так строго, но не помешает.
|
||||||
|
|
||||||
|
# 4. РЕСУРСЫ (Без лимитов для разработки)
|
||||||
|
# Удаляем секцию ограничений, чтобы локально использовать все доступные ресурсы хоста.
|
||||||
|
# deploy:
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# cpus: ...
|
||||||
|
# memory: ...
|
||||||
|
# mem_limit: ...
|
||||||
|
|
||||||
148
docker-compose.prod.yml
Normal file
148
docker-compose.prod.yml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Docker Compose для PRODUCTION
|
||||||
|
# Этот файл запускается на боевом сервере.
|
||||||
|
# Вариант 1 (если переименовали в docker-compose.yml): docker compose up -d
|
||||||
|
# Вариант 2 (если оставили имя): docker compose -f docker-compose.prod.yml up -d
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# В новой версии Docker не нужно
|
||||||
|
# version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# --- ОCНОВНОЙ СЕРВИС: DJANGO + GUNICORN + WHITENOISE ---
|
||||||
|
web:
|
||||||
|
# Имя контейнера
|
||||||
|
container_name: cadpoint-backend
|
||||||
|
|
||||||
|
# 1. ОБРАЗ
|
||||||
|
# В продакшене мы используем готовый, собранный образ из реестра (Gitea)
|
||||||
|
image: git.cube2.ru/erjemin/2021-cadpoint-ru:latest
|
||||||
|
# Если образа в gitae нет, то перенести весь код в прод и можно собирать локально:
|
||||||
|
# build: .
|
||||||
|
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
# 2. Метки для Watchtower (авто-обновление)
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.scope=cadpoint-scope"
|
||||||
|
|
||||||
|
# 3. КОМАНДА ЗАПУСКА (Замена entrypoint.sh)
|
||||||
|
# Выполняем цепочку команд внутри контейнера при запуске:
|
||||||
|
# a. Миграции
|
||||||
|
# b. Collectstatic
|
||||||
|
# с. Создаем папку nginx в примонтированном томе конфигов (если нет)
|
||||||
|
# d. Копирование конфига Nginx с авто-заменой путей через sed (замену реального пути на хосте получаем
|
||||||
|
# через переменную окружения HOST_PROJECT_PATH)
|
||||||
|
# e. Инициализация боевого конфига (если нет)
|
||||||
|
# f. Создаем папку для ошибок и копируем туда HTML error pages и их ассеты (там их увидит Nginx хоста)
|
||||||
|
# — иконки, SVG-иллюстрации и under_reconstruction тоже должны лежать рядом, чтобы Nginx мог их отдать
|
||||||
|
# g. Запуск Gunicorn
|
||||||
|
command: >
|
||||||
|
sh -c "python manage.py migrate --noinput &&
|
||||||
|
python manage.py collectstatic --noinput --clear &&
|
||||||
|
mkdir -p /nginx_configs_host/nginx &&
|
||||||
|
sed \"s|/home/user/app/cadpoint-site|${HOST_PROJECT_PATH:-/home/default_user/projects/cadpoint-site}|g\" /nginx_configs_host/nginx/cadpoint-app--external-nginx.conf > /nginx_configs_host/nginx/nginx_cadpoint.conf.example &&
|
||||||
|
if [ ! -f /nginx_configs_host/nginx/cadpoint-app--external-nginx.conf ]; then
|
||||||
|
cp /nginx_configs_host/nginx/nginx_cadpoint.conf.example /nginx_configs_host/nginx/cadpoint-app--external-nginx.conf;
|
||||||
|
echo 'INIT: Created new nginx config with correct paths';
|
||||||
|
fi &&
|
||||||
|
ERROR_DIR=/home/app/web/public/media/_error &&
|
||||||
|
mkdir -p "$$ERROR_DIR/svgs" "$$ERROR_DIR/img" &&
|
||||||
|
for code in 400 401 403 404 413 429 500 502 503 504; do
|
||||||
|
cp /home/app/web/cadpoint/templates/$${code}.html "$$ERROR_DIR/$${code}.html";
|
||||||
|
done &&
|
||||||
|
cp /home/app/web/cadpoint/templates/under_reconstruction.html "$$ERROR_DIR/under_reconstruction.html" &&
|
||||||
|
cp /home/app/web/public/static/svgs/favicon.svg "$$ERROR_DIR/svgs/favicon.svg" &&
|
||||||
|
cp /home/app/web/public/static/svgs/xxx-error.svg "$$ERROR_DIR/svgs/xxx-error.svg" &&
|
||||||
|
cp /home/app/web/public/static/svgs/404-error.svg "$$ERROR_DIR/svgs/404-error.svg" &&
|
||||||
|
cp /home/app/web/public/static/svgs/500-error.svg "$$ERROR_DIR/svgs/500-error.svg" &&
|
||||||
|
cp /home/app/web/public/static/svgs/cappoint_under_reconstruction.svg "$$ERROR_DIR/svgs/cappoint_under_reconstruction.svg" &&
|
||||||
|
cp /home/app/web/public/static/img/favicon.png "$$ERROR_DIR/img/favicon.png" &&
|
||||||
|
cp /home/app/web/public/static/img/favicon.ico "$$ERROR_DIR/img/favicon.ico" &&
|
||||||
|
gunicorn --workers 2 --bind 0.0.0.0:8000 cadpoint.wsgi:application"
|
||||||
|
|
||||||
|
# 4. Проброс портов (Внешний Nginx -> localhost:8050)
|
||||||
|
ports:
|
||||||
|
# Слушаем только на localhost хоста, чтобы закрыть прямой доступ из интернета к Gunicorn
|
||||||
|
- "127.0.0.1:8050:8000"
|
||||||
|
|
||||||
|
# 5. Тома (Volumes)
|
||||||
|
volumes:
|
||||||
|
# База данных
|
||||||
|
# Монтируем папку database с хоста в папку с базой внутри контейнера.
|
||||||
|
# Путь в контейнере: /home/app/web/database (так как Django ищет базу в BASE_DIR.parent/database)
|
||||||
|
- ./database:/home/app/web/database
|
||||||
|
|
||||||
|
# Медиа, служебные error-pages и их ассеты лежат в папке `media` на хосте.
|
||||||
|
- ./media:/home/app/web/public/media
|
||||||
|
|
||||||
|
# Конфиги (Монтируем папку ./config с хоста в /nginx_configs_host внутри контейнера)
|
||||||
|
# Это нужно, чтобы скрипт запуска мог положить туда .example конфиг и прочитать боевой конфиг.
|
||||||
|
- ./config:/nginx_configs_host
|
||||||
|
|
||||||
|
# 6. Пользователь и права
|
||||||
|
user: "1000:1000"
|
||||||
|
|
||||||
|
# Когда нужна отладка процессов внутри контейнера, можно временно раскомментировать эту строку и запустить контейнер с правами root.
|
||||||
|
# cap_add:
|
||||||
|
# - SYS_PTRACE
|
||||||
|
|
||||||
|
# 7. Переменные окружения
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DJANGO_SETTINGS_MODULE=cadpoint.settings
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
# Передаем переменную с путем на хосте внутрь контейнера, чтобы sed мог её использовать
|
||||||
|
- HOST_PROJECT_PATH=${HOST_PROJECT_PATH:-/home/default_user/projects/cadpoint-site}
|
||||||
|
|
||||||
|
# 8. Проверка здоровья контейнера (Healthcheck)
|
||||||
|
# Docker будет периодически проверять статус контейнера. Это критично для Watchtower!
|
||||||
|
# Если контейнер объявлен "unhealthy", Watchtower сначала остановит старый образ, потом запустит новый.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"]
|
||||||
|
interval: 3m # Проверка каждые 3 минуты
|
||||||
|
timeout: 12s # Таймаут ответа - 12 секунды
|
||||||
|
start_period: 20s # Даем 20 секунд на стартап перед первой проверкой
|
||||||
|
retries: 3 # Unhealthy после 3 неудачных попыток
|
||||||
|
|
||||||
|
# 9. Логирование (Ротация)
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# 10. Ресурсы
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.50'
|
||||||
|
memory: 512M
|
||||||
|
mem_limit: 512m
|
||||||
|
|
||||||
|
# --- WATCHTOWER: АВТО-ОБНОВЛЕНИЕ ОБРАЗОВ ---
|
||||||
|
# Следит за реестром Gitea и обновляет контейнер web, если появился новый image
|
||||||
|
watchtower:
|
||||||
|
image: containrrr/watchtower
|
||||||
|
container_name: cadpoint_watchtower
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
# Токен/Логин для вашего приватного реестра (нужно добавить в .env!)
|
||||||
|
# REPO_USER и REPO_PASS должны быть в .env файле на сервере
|
||||||
|
- REPO_USER=${REPO_USER}
|
||||||
|
- REPO_PASS=${REPO_PASS}
|
||||||
|
- WATCHTOWER_SCOPE=cadpoint-scope
|
||||||
|
- WATCHTOWER_CLEANUP=true # Удалять старые образы после обновления
|
||||||
|
- DOCKER_API_VERSION=1.44
|
||||||
|
# Дополнительные опции для правильной работы с healthcheck
|
||||||
|
- WATCHTOWER_WAIT_ON_TIMEOUT=60 # Ждем 60 сек пока контейнер станет healthy перед финализацией
|
||||||
|
- WATCHTOWER_LIFECYCLE_HOOKS=true # Включаем lifecycle hooks для graceful shutdown
|
||||||
|
command: --interval 1800 --cleanup # Проверять каждые 30 минут
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
50
poetry.lock
generated
50
poetry.lock
generated
@@ -392,6 +392,29 @@ beautifulsoup4 = ">=4.10.0"
|
|||||||
lxml = ">=4.9.0"
|
lxml = ">=4.9.0"
|
||||||
regex = ">=2022.1.18"
|
regex = ">=2022.1.18"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gunicorn"
|
||||||
|
version = "25.3.0"
|
||||||
|
description = "WSGI HTTP Server for UNIX"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
files = [
|
||||||
|
{file = "gunicorn-25.3.0-py3-none-any.whl", hash = "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660"},
|
||||||
|
{file = "gunicorn-25.3.0.tar.gz", hash = "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
packaging = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
eventlet = ["eventlet (>=0.40.3)"]
|
||||||
|
fast = ["gunicorn_h1c (>=0.6.3)"]
|
||||||
|
gevent = ["gevent (>=24.10.1)"]
|
||||||
|
http2 = ["h2 (>=4.1.0)"]
|
||||||
|
setproctitle = ["setproctitle"]
|
||||||
|
testing = ["coverage", "eventlet (>=0.40.3)", "gevent (>=24.10.1)", "h2 (>=4.1.0)", "httpx[http2]", "pytest", "pytest-asyncio", "pytest-cov", "uvloop (>=0.19.0)"]
|
||||||
|
tornado = ["tornado (>=6.5.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lxml"
|
name = "lxml"
|
||||||
version = "6.0.2"
|
version = "6.0.2"
|
||||||
@@ -547,6 +570,17 @@ html-clean = ["lxml_html_clean"]
|
|||||||
html5 = ["html5lib"]
|
html5 = ["html5lib"]
|
||||||
htmlsoup = ["BeautifulSoup4"]
|
htmlsoup = ["BeautifulSoup4"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "26.0"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
|
||||||
|
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.2.0"
|
version = "12.2.0"
|
||||||
@@ -941,7 +975,21 @@ files = [
|
|||||||
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "whitenoise"
|
||||||
|
version = "6.12.0"
|
||||||
|
description = "Radically simplified static file serving for WSGI applications"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
files = [
|
||||||
|
{file = "whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2"},
|
||||||
|
{file = "whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.12,<3.13"
|
python-versions = ">=3.12,<3.13"
|
||||||
content-hash = "8284fc2ef5f2a06d27b41da40cc2067920b8fd5fed8f23621b777a15d8ca4559"
|
content-hash = "f0a03e4a068c519c6f13ec9816ea519c95ff650b1aa85915f17bc8ca364bf20a"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Эта папка предназначена для хранения статических файлов, таких как изображения, стили CSS и JavaScript файлы.
|
Эта папка предназначена для хранения статических файлов, таких как изображения, стили CSS и JavaScript файлы.
|
||||||
Папка будет внутри контейнера, а файлы внутри папки будут доступны с помощью gunicorn и whitenoise на хосте.
|
Папка будет внутри контейнера, а файлы внутри папки будут доступны с помощью gunicorn и whitenoise на хосте.
|
||||||
|
|
||||||
Но в случае сбоя контейнера, ошибок Djанго и ошибок 404 при доступе в media (которые будут в папке `media` на
|
Но в случае сбоя контейнера, ошибок Django и ошибок 404 при доступе в media (которые будут в папке `media` на
|
||||||
внешнем хосте с доступом через nginx), то эти файлы могут стать недоступны. Таким образом, файлы необхдимые
|
внешнем хосте с доступом через nginx), то эти файлы могут стать недоступны. Таким образом, файлы необхдимые
|
||||||
для отображения кастовых страниц ошибок 400, 403, 404, 500 и других должны быть скопированы на внешний хост,
|
для отображения кастовых страниц ошибок 400, 403, 404, 500 и других должны быть скопированы на внешний хост,
|
||||||
в папку `media/_error` в момент запуска контейнера (с помощью `entrypoint.sh` или инструкций `command`
|
в папку `media/_error` в момент запуска контейнера (с помощью `entrypoint.sh` или инструкций `command`
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ django-mptt = "^0.18.0"
|
|||||||
pytils = "^0.4.4"
|
pytils = "^0.4.4"
|
||||||
django-select2 = "^8.4.8"
|
django-select2 = "^8.4.8"
|
||||||
etpgrf = "^0.1.6"
|
etpgrf = "^0.1.6"
|
||||||
|
gunicorn = "^25.3.0"
|
||||||
|
whitenoise = "^6.12.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
django-debug-toolbar = "^6.3"
|
django-debug-toolbar = "^6.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user