Services Deployment
Создание и настройка каждого сервиса в Dokploy. Порядок деплоя критичен.
1. Порядок деплоя
Сервисы зависят друг от друга — деплой строго последовательный:
| Порядок | Сервис | Причина зависимости |
|---|---|---|
| 1 | Backend | Все зависят от API |
| 2 | Frontend | Нужен VITE_API_URL |
| 3 | Admin | Нужен VITE_API_URL |
| 4 | Redirect Service | Нужна та же БД |
| 5 | Static Nginx | Shared volume с Backend |
| 6 | Docs Site | Нужен API для OpenAPI spec |
2. Общая процедура для каждого сервиса
В Dokploy UI для каждого сервиса:
- Projects → Выбрать проект "goLoot" (или создать)
- New Service → Application
- Настроить Provider (вкладка General, верхняя секция):
- Source: GitHub → выбрать репозиторий
- Branch:
main - Build Path:
/(корень репо, одинаково для всех) - Trigger Type: On Push
- Watch Paths: зависит от сервиса (см. таблицы ниже)
- Настроить Build Type (вкладка General, нижняя секция):
- Build Type: Dockerfile
- Docker File: путь к Dockerfile (см. таблицу ниже)
- Docker Context Path:
.(корень репо, одинаково для всех)
- Environment Variables: задать переменные (вкладка Environment)
- Domains: привязать домен с HTTPS (вкладка Domains)
- Deploy
Build Path и Docker Context Path
В Dokploy есть два похожих поля — оба должны указывать на корень монорепо:
| Поле | Где находится | Значение | Назначение |
|---|---|---|---|
| Build Path | General → Provider | / | Базовый путь в репозитории |
| Docker Context Path | General → Build Type | . | Контекст для Docker build |
Все Dockerfiles используют контекст . (корень монорепо), потому что:
- Backend и Redirect Service копируют
shared/иbackend/prisma/ - Frontend и Admin копируют
shared/ - Поэтому Docker Context Path всегда
., а Docker File — путь к конкретному Dockerfile
3. Сервис: Backend
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | backend | General → Provider |
| Build Path | / | General → Provider |
| Docker File | backend/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 4000 | Domains |
Domain
| Host | Port | HTTPS |
|---|---|---|
api.goloot.online | 4000 | Let's Encrypt |
Environment Variables
Полный список в Environment Variables. Минимум для запуска:
NODE_ENV=production
PORT=4000
HOST=0.0.0.0
# Database
DB_ENV=prod
DATABASE_URL_PROD=postgresql://goloot:PASSWORD@postgresql:5432/goloot
# JWT
JWT_SECRET=your-secure-jwt-secret-min-32-chars
JWT_EXPIRES_IN=7d
# Telegram
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_BOT_USERNAME=YourBotUsername
TELEGRAM_APP_NAME=your_app_name
TELEGRAM_MINI_APP_URL=https://goloot.online
TELEGRAM_WEBHOOK_URL=https://api.goloot.online/api/telegram/webhook
# URLs
STATIC_URL=https://static.goloot.online
WEB_APP_URL=https://goloot.online
REDIRECT_START_URL=https://start.goloot.online
REDIRECT_PUSH_URL=https://r.goloot.online
# Redis (опционально)
REDIS_URL=redis://:PASSWORD@redis:6379
# Quiz Generation (SQLite)
QUIZ_SQLITE_PATH=/var/data/goloot/rust_data.db
# Logging
LOG_LEVEL=info
Health Check
Health Check настраивается через Swarm Settings в Dokploy:
- Dokploy → Backend Service → Advanced
- Найти секцию Cluster Settings → нажать кнопку Swarm Settings
- В левом меню выбрать Health Check
- Заполнить поля:
Test Commands — нажать Add Command дважды и добавить:
| # | Значение |
|---|---|
| 1 | CMD-SHELL |
| 2 | wget --no-verbose --tries=1 --spider http://localhost:4000/health |
В production-образе Backend установлен wget, но не curl (Alpine-образ с минимальными зависимостями). Если использовать curl — health check будет падать с ошибкой "not found".
Остальные поля:
| Поле | Значение | Описание |
|---|---|---|
| Interval | 30000000000 | 30 сек между проверками |
| Timeout | 10000000000 | 10 сек на ответ |
| Start Period | 30000000000 | 30 сек на запуск (Prisma init, plugins) |
| Retries | 3 | 3 неудачи → контейнер помечается unhealthy |
Docker Swarm принимает время в наносекундах. 1 секунда = 1,000,000,000 нс. Поэтому 30 секунд = 30000000000.
- Нажать Save Health Check, затем Redeploy
Проверка после деплоя:
curl -s https://api.goloot.online/health
# {"status":"ok","timestamp":"..."}
Volumes
В Dokploy → Backend Service → Advanced → Volumes → нажать Add Volume.
Нужно добавить 2 volume разных типов:
Volume 1: Static files (shared с Static Nginx)
| Поле | Значение |
|---|---|
| Mount Type | Volume Mount |
| Volume Name | goloot_static |
| Mount Path | /static |
Заливка static files на VPS (пошагово)
Перед первым деплоем нужно заполнить goloot_static volume изображениями (предметы Rust, баннеры, иконки и т.д.). Без этого статика не будет отображаться.
1. Создать Docker volume на VPS
docker volume create goloot_static
2. Архивировать static на локальной машине
cd /path/to/goloot/tmp && tar -czf static.tar.gz static/
Архив включает images/ (предметы, баннеры, аватары, квизы) и guides/. Размер ~83 MB.
3. Скопировать архив на VPS
scp static.tar.gz root@YOUR_SERVER_IP:/tmp/
4. Распаковать в Docker volume и выдать права
# Распаковать (--strip-components=1 убирает папку static/ из архива)
tar -xzf /tmp/static.tar.gz -C /var/lib/docker/volumes/goloot_static/_data/ --strip-components=1
# Выдать права (1001:1001 = nodejs user в Backend-контейнере)
chown -R 1001:1001 /var/lib/docker/volumes/goloot_static/_data/
# Проверить
ls -la /var/lib/docker/volumes/goloot_static/_data/
# Ожидаемый вывод: guides/ images/ — владелец 1001:1001
# Удалить архив
rm /tmp/static.tar.gz
Backend-контейнер работает под пользователем nodejs (UID=1001). Если владелец файлов другой (например ubuntu:docker после SCP) — Backend не сможет записывать новые файлы (аватары, баннеры, кейсы). Всегда выполняйте chown -R 1001:1001 после заливки.
Volume 2: SQLite database (quiz generation)
| Поле | Значение |
|---|---|
| Mount Type | Bind Mount |
| Host Path | /var/data/goloot |
| Mount Path | /var/data/goloot |
Файл /var/data/goloot/rust_data.db (~29 MB) содержит данные предметов Rust для генерации квизов. Без него quiz generation не работает.
Владелец: 1001:1001 (совпадает с nodejs пользователем в контейнере).
Режим: read-only — Backend только читает эту базу.
Подготовка SQLite на хосте (пошагово)
Почему 1001:1001?
Backend-контейнер работает под непривилегированным пользователем nodejs (UID=1001, GID=1001), заданным в backend/Dockerfile. Файлы, смонтированные через bind mount, сохраняют владельца с хоста. Если владелец не совпадает — контейнер не сможет прочитать файл и quiz generation упадёт с ошибкой.
Первоначальная настройка
# 1. Создать директорию на хосте
sudo mkdir -p /var/data/goloot
# 2. Скопировать файл базы (из локальной машины или другого источника)
# Вариант A: с локальной машины через scp
scp quizzes/data/rust_data.db root@YOUR_SERVER_IP:/var/data/goloot/
# Вариант B: файл уже на сервере — просто скопировать
sudo cp /path/to/rust_data.db /var/data/goloot/
# 3. Назначить владельца — ОБЯЗАТЕЛЬНО
sudo chown -R 1001:1001 /var/data/goloot
# 4. Проверить
ls -la /var/data/goloot/rust_data.db
# Ожидаемый вывод:
# -rw-r--r-- 1 1001 1001 29M ... rust_data.db
Обновление файла базы
При обновлении rust_data.db (новые предметы, патч Rust):
# 1. Загрузить новый файл
scp quizzes/data/rust_data.db root@YOUR_SERVER_IP:/var/data/goloot/
# 2. Восстановить владельца (scp сбрасывает на root:root)
sudo chown 1001:1001 /var/data/goloot/rust_data.db
# 3. Перезапуск контейнера НЕ нужен — SQLite открывается при каждом запросе
Диагностика
Если quiz generation не работает — проверь:
# Файл существует?
ls -la /var/data/goloot/rust_data.db
# Владелец правильный? (должно быть 1001 1001)
stat -c '%u:%g' /var/data/goloot/rust_data.db
# Bind mount подключён в контейнере?
# (в Dokploy → Backend → Advanced → Volumes должен быть /var/data/goloot)
docker exec <container_id> ls -la /var/data/goloot/rust_data.db
# Env переменная задана?
# QUIZ_SQLITE_PATH=/var/data/goloot/rust_data.db
| Симптом | Причина | Решение |
|---|---|---|
SQLITE_CANTOPEN | Файл не найден или нет прав | chown 1001:1001 + проверить bind mount |
no such table: items | Файл пустой или повреждён | Заново скопировать rust_data.db |
| Quiz generation молча не работает | QUIZ_SQLITE_PATH не задан | Добавить env переменную в Dokploy |
Структура папок в goloot_static volume
/static/
├── images/ # Основная папка
│ ├── grain.webp # Текстура UI (grain overlay)
│ ├── steam.webp # Steam иконка
│ ├── SkillTree/ # Иконки дерева навыков Rust
│ │ ├── Build_Craft/
│ │ ├── Combat/
│ │ ├── Cooking/
│ │ ├── Harvesting/
│ │ ├── Medical/
│ │ ├── Mining/
│ │ ├── Raiding/
│ │ ├── Scavenging/
│ │ ├── Skinning/
│ │ ├── Team/
│ │ ├── Underwater/
│ │ ├── Vehicles/
│ │ └── Woodcutting/
│ ├── opengraph/
│ │ ├── referrals/ # OG-превью для реферальных ссылок
│ │ ├── utm/ # OG-превью для UTM кампаний
│ │ └── content/ # OG-превью для контента
│ ├── banners/ # Рекламные баннеры (загрузка через админку)
│ ├── push/ # Изображения для push-уведомлений
│ ├── quizzes/ # Изображения для вопросов квизов
│ ├── cases/ # Изображения кейсов (динамически, Admin API)
│ ├── avatars/ # Telegram аватары (кэш 24ч)
│ └── items/ # Игровые предметы (см. ниже)
│ ├── currency/ # Иконки валют
│ │ ├── scrap.webp # Иконка scrap
│ │ └── xp.webp # Иконка XP
│ ├── badges/ # Badge overlays для предметов
│ │ ├── blueprintbase.webp # Badge для BLUEPRINT
│ │ └── fragment_*_tier_*.webp # Badge для FRAGMENT (по тирам)
│ ├── buffs/ # Иконки баффов
│ │ ├── scrap.webp # Бафф на scrap
│ │ ├── xp.webp # Бафф на XP
│ │ └── shield.webp # Бафф на защиту
│ ├── resources/ # Иконки ресурсов крафта (17 файлов)
│ ├── rust/ # Предзаполненные изображения Rust-предметов
│ │ ├── Containers/
│ │ ├── Scientists/
│ │ ├── Animals/
│ │ ├── Weapons/
│ │ ├── Tools/
│ │ ├── Attire/
│ │ ├── Medical/
│ │ ├── Food/
│ │ ├── Resources/
│ │ ├── Construction/
│ │ ├── Electrical/
│ │ ├── Components/
│ │ ├── Ammo/
│ │ ├── Items/
│ │ ├── Traps/
│ │ ├── Fun/
│ │ └── Misc/
│ └── skin/ # Скины (загрузка через Admin API)
│ ├── clothing/
│ ├── armor/
│ ├── weapon/
│ └── other/
└── guides/ # Гайды (создаются при деплое через Dockerfile)
Источники данных в items/:
items/rust/— предзаполненные PNG-изображения Rust-предметов (загружены вручную)items/skin/— создаются динамически через Admin API при загрузке скиновitems/currency/,items/badges/,items/buffs/,items/resources/— статичные ассеты UI
Права: Владелец всех файлов: 1001:1001 (nodejs user в контейнере).
Watch Paths
В Dokploy → Advanced → Watch Paths (для автодеплоя):
backend/**
shared/**
После первого деплоя
Применить миграции и seeds — см. Database Setup: Инициализация.
4. Сервис: Frontend
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | frontend | General → Provider |
| Build Path | / | General → Provider |
| Docker File | frontend/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 80 | Domains |
Domain
| Host | Port | HTTPS |
|---|---|---|
goloot.online | 80 | Let's Encrypt |
Build-time Variables
Frontend использует build-time переменные (ARG в Dockerfile). В Dokploy это Build-time Variables (не Environment):
VITE_API_URL=https://api.goloot.online
VITE_TELEGRAM_BOT_USERNAME=YourBotUsername
VITE_TELEGRAM_CHANNEL_USERNAME=YourChannelUsername
VITE_REDIRECT_START_URL=https://start.goloot.online
Полный список build-time variables
| Variable | Описание | Пример |
|---|---|---|
VITE_API_URL | Backend API URL | https://api.goloot.online |
VITE_TELEGRAM_BOT_USERNAME | Username бота | GoLootBot |
VITE_TELEGRAM_CHANNEL_USERNAME | Username канала | goloot_channel |
VITE_SEQUENTIAL_API_TIMEOUT | Таймаут API (мс) | 5000 |
VITE_SEQUENTIAL_TOTAL_TIMEOUT | Общий таймаут (мс) | 15000 |
VITE_AD_FETCH_TIMEOUT | Таймаут рекламы (мс) | 3000 |
VITE_FAVORITES_CACHE_TTL | TTL кэша избранного | 300000 |
VITE_POPULARITY_NEW_THRESHOLD | Порог "new" популярности | 5 |
VITE_POPULARITY_NORMAL_THRESHOLD | Порог "normal" | 20 |
VITE_POPULARITY_HOT_THRESHOLD | Порог "hot" | 50 |
VITE_POPULARITY_EPIC_THRESHOLD | Порог "epic" | 100 |
VITE_REDIRECT_START_URL | URL redirect-сервиса (referral/UTM) | https://start.goloot.online |
VITE_BANNER_HIDE_DURATION | Скрытие баннера (мс) | 86400000 |
Watch Paths
frontend/**
shared/**
5. Сервис: Admin
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | admin | General → Provider |
| Build Path | / | General → Provider |
| Docker File | admin/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 80 | Domains |
Domain
| Host | Port | HTTPS |
|---|---|---|
admin.goloot.online | 80 | Let's Encrypt |
Build-time Variables
VITE_API_URL=https://api.goloot.online
VITE_STATIC_URL=https://static.goloot.online
VITE_REDIRECT_START_URL=https://start.goloot.online
VITE_REDIRECT_PUSH_URL=https://r.goloot.online
VITE_GRAFANA_URL=https://grafana.goloot.online
Watch Paths
admin/**
shared/**
6. Сервис: Redirect Service
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | redirect-service | General → Provider |
| Build Path | / | General → Provider |
| Docker File | redirect-service/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 3001 | Domains |
Domains
| Host | Port | HTTPS | Назначение |
|---|---|---|---|
start.goloot.online | 3001 | Let's Encrypt | Реферальные/UTM ссылки (user-facing) |
r.goloot.online | 3001 | Let's Encrypt | Push tracking (internal) |
Redirect Service обслуживает оба домена:
start.goloot.online— для ссылок, которые видит пользователь (рефералы, UTM). UX: "start" = начни здесьr.goloot.online— для технических ссылок (push-рассылки). Пользователь их не видит напрямую
Environment Variables
NODE_ENV=production
PORT=3001
HOST=0.0.0.0
LOG_LEVEL=info
DATABASE_URL=postgresql://goloot:PASSWORD@postgresql:5432/goloot
STATIC_URL=https://static.goloot.online
START_URL=https://start.goloot.online
TELEGRAM_BOT_USERNAME=YourBotUsername
TELEGRAM_APP_NAME=your_app_name
Redirect Service использует DATABASE_URL напрямую (не DATABASE_URL_PROD), потому что у него простой config без env-switching.
Watch Paths
redirect-service/**
backend/prisma/**
7. Сервис: Static Nginx
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | static-nginx | General → Provider |
| Build Path | / | General → Provider |
| Docker File | static-nginx/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 80 | Domains |
Domain
| Host | Port | HTTPS |
|---|---|---|
static.goloot.online | 80 | Let's Encrypt |
Volume
Static Nginx использует Docker volume для хранения файлов, общий с Backend.
В Dokploy → Static Nginx Service → Advanced → Volumes → нажать Add Volume:
| Поле | Значение |
|---|---|
| Mount Type | Volume Mount |
| Volume Name | goloot_static |
| Mount Path | /usr/share/nginx/html |
Backend записывает файлы в /static/ (изображения, аватары, баннеры). Static Nginx читает их из /usr/share/nginx/html/. Это один и тот же Docker volume goloot_static. Настройка Backend volume — в секции Backend → Volumes.
# Создать volume (если не создан автоматически)
docker volume create goloot_static
Watch Paths
static-nginx/**
8. Сервис: Docs Site
Настройки в Dokploy
| Параметр | Значение | Где в UI |
|---|---|---|
| Name | docs-site | General → Provider |
| Build Path | / | General → Provider |
| Docker File | docs-site/Dockerfile | General → Build Type |
| Docker Context Path | . | General → Build Type |
| Port | 80 | Domains |
| Autodeploy | ВЫКЛЮЧЕН | General → Deploy Settings |
Docs Site деплоится через GitHub Actions → webhook, а не по push в main. Причина: сборка Docusaurus требует ~4 GB RAM и может упасть на VPS. Сборка происходит на GitHub Actions runners.
Domain
| Host | Port | HTTPS |
|---|---|---|
docs.goloot.online | 80 | Let's Encrypt |
Branch
| Параметр | Значение |
|---|---|
| Branch | docs-build (не main!) |
Docs Site деплоится из ветки docs-build, куда GitHub Actions пушит собранные статические файлы. Это не main.
Webhook
После создания сервиса скопируй Deploy Webhook URL из Dokploy и добавь в GitHub:
- Repository → Settings → Secrets →
DOKPLOY_DOCS_WEBHOOK
Подробнее: CI/CD Setup
9. Проверка после деплоя
Health Checks
# Backend
curl -s https://api.goloot.online/health
# {"status":"ok","timestamp":"..."}
# Frontend
curl -s -o /dev/null -w "%{http_code}" https://goloot.online
# 200
# Admin
curl -s -o /dev/null -w "%{http_code}" https://admin.goloot.online
# 200
# Redirect Service
curl -s -o /dev/null -w "%{http_code}" https://start.goloot.online
# 200 (основной домен)
curl -s -o /dev/null -w "%{http_code}" https://r.goloot.online
# 200 (push tracking домен)
# Static
curl -s https://static.goloot.online/health
# healthy
# Docs
curl -s -o /dev/null -w "%{http_code}" https://docs.goloot.online
# 200
Логи
В Dokploy UI → Service → Logs — проверь что нет ошибок при старте.
Или через CLI:
# Логи backend (последние 50 строк)
docker logs $(docker ps -q -f name=backend) --tail 50
# Список всех контейнеров
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
10. Telegram Bot Webhook
После деплоя Backend нужно зарегистрировать webhook, чтобы Telegram отправлял обновления бота на наш сервер.
Когда нужно регистрировать
- Первый деплой с новым ботом
- Смена домена или URL webhook
- Смена
TELEGRAM_WEBHOOK_SECRET - После удаления webhook (например, при отладке через polling)
Регистрация
curl -X POST "https://api.telegram.org/bot<BOT_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.goloot.online/api/telegram/webhook",
"secret_token": "<TELEGRAM_WEBHOOK_SECRET>"
}'
<BOT_TOKEN>— токен бота изTELEGRAM_BOT_TOKEN(получить у @BotFather)<TELEGRAM_WEBHOOK_SECRET>— секрет из env переменнойTELEGRAM_WEBHOOK_SECRET
secret_token — защита webhook от поддельных запросов. Telegram присылает его в заголовке X-Telegram-Bot-Api-Secret-Token, backend проверяет совпадение. Без него webhook принимает запросы от кого угодно.
Ожидаемый ответ:
{"ok": true, "result": true, "description": "Webhook was set"}
Проверка статуса
curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getWebhookInfo" | jq .
Важные поля в ответе:
url— зарегистрированный URLhas_custom_certificate— должно бытьfalse(Let's Encrypt)pending_update_count— сколько обновлений в очередиlast_error_message— последняя ошибка (если есть)
Удаление webhook
Если нужно временно переключиться на polling (для локальной отладки):
curl -X POST "https://api.telegram.org/bot<BOT_TOKEN>/deleteWebhook"
После удаления webhook бот перестанет получать обновления на сервере. Не забудь зарегистрировать заново после отладки.
11. Чеклист
- Backend задеплоен и
/healthотвечает - Миграции применены
- Seeds загружены
- Frontend задеплоен и открывается
- Admin задеплоен и открывается
- Redirect Service задеплоен
- Static Nginx задеплоен с shared volume
- Docs Site задеплоен из ветки
docs-build - Telegram webhook зарегистрирован
- Все HTTPS-сертификаты получены
Related
- Database Setup — предыдущий шаг
- Environment Variables — полный справочник переменных
- Monitoring Setup — следующий шаг