mod: update production docker deployment
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 12s
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 12s
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
# Секреты и локальные настройки не должны попадать в контейнерный контекст.
|
||||
.env
|
||||
.env.*
|
||||
.env.sample
|
||||
|
||||
# Виртуальное окружение и служебные артефакты Python.
|
||||
.venv/
|
||||
@@ -25,6 +26,7 @@ htmlcov/
|
||||
# Локальные базы и дампы SQLite в контейнер не тащим.
|
||||
*.sqlite3
|
||||
database/
|
||||
media/
|
||||
|
||||
# Локальная сборка фронтенда пока не нужна в Docker-контексте.
|
||||
# Если позже соберём frontend внутри Docker, это правило можно пересмотреть.
|
||||
@@ -37,10 +39,8 @@ public/media/
|
||||
*.md
|
||||
**/.gitignore
|
||||
|
||||
# Будущие Dockerfile и основной compose-файл обычно храним в репозитории,
|
||||
# поэтому их НЕ игнорируем. Игнорируем только локальные override-варианты.
|
||||
docker-compose.override.yml
|
||||
compose.override.yml
|
||||
docker-compose.local.yml
|
||||
compose.local.yml
|
||||
# Репозиторные и оркестрационные файлы не нужны внутри runtime-образа.
|
||||
.gitea/
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
|
||||
|
||||
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: ${{ secrets.REPO_USER }}
|
||||
password: ${{ secrets.REPO_PASS }}
|
||||
|
||||
# Извлечение метаданных (тегов и лейблов) для 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: 900 # 15 минут на всю сборку
|
||||
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; # Всегда редиректим на основной домен
|
||||
# }
|
||||
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"
|
||||
Reference in New Issue
Block a user