chore: prepare production deployment

This commit is contained in:
2026-07-20 05:24:12 +05:00
parent 2e3550a4dc
commit 77ed2d8423
31 changed files with 681 additions and 496 deletions
+91 -30
View File
@@ -1,6 +1,6 @@
# Развёртывание Flamy Trade
Документ описывает production-развёртывание сайта `trade.flamy.studio` без ML-runtime и без будущего API. Эти части подключаются отдельно.
Документ описывает production-развёртывание сайта `trade.flamy.studio` без ML-runtime и будущего API. Эти части подключаются отдельным этапом.
## Схема
@@ -9,11 +9,11 @@ Internet
-> Router :80/:443
-> edge-proxy VM / Caddy
-> 10.20.0.20:18082
-> flamy_trade_nginx
-> flamy_trade_app:3000
-> flamy_trade-nginx-1
-> flamy_trade-app-1:3000
```
На VM публикуется только nginx. Node-приложение доступно только во внутренней Docker-сети.
Compose project name зафиксирован как `flamy_trade`. `container_name` не задаётся, чтобы не ломать стандартное управление Compose.
## Переменные
@@ -23,16 +23,23 @@ Internet
PUBLIC_SITE_URL=https://trade.flamy.studio
PUBLIC_SITE_NAME=Flamy Trade
PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка.
PUBLIC_DASHBOARD_DATA_MODE=research-static
HOST=0.0.0.0
PORT=3000
ORIGIN=https://trade.flamy.studio
BODY_SIZE_LIMIT=1M
SHUTDOWN_TIMEOUT=15
APP_STOP_GRACE_PERIOD=20s
APP_IMAGE_REPOSITORY=registry.example.com/flamy-trade
APP_IMAGE_TAG=<git-sha>
BUILD_DATE=<utc-iso-date>
VCS_REF=<git-sha>
APP_HOST_IP=10.20.0.20
APP_PUBLISHED_PORT=18082
APP_IMAGE_TAG=<git-sha>
NGINX_IMAGE=nginxinc/nginx-unprivileged:1.30.4-alpine-perl@sha256:36fa38fb8f34faba45e7e1857644d6561f2faf45e7ce6b15e97d1a2a1682a0cd
```
Перед фиксацией порта проверьте, что `18082` свободен:
@@ -42,7 +49,7 @@ sudo ss -lntup | grep ':18082'
docker ps --format 'table {{.Names}}\t{{.Ports}}'
```
## Сборка
## Проверки перед сборкой
```bash
pnpm install --frozen-lockfile
@@ -51,14 +58,22 @@ pnpm lint
pnpm check
pnpm build
pnpm audit
docker compose config
```
`pnpm outdated` должен проверяться вручную. На текущем стеке допустимы только осознанные удержания: `@types/node` на ветке 24 под Node 24 LTS и TypeScript 6.x до обновления peer constraints в SvelteKit/typescript-eslint.
## Сборка
Docker:
Перед сборкой image задайте provenance:
```bash
export BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
export VCS_REF="$(git rev-parse HEAD)"
export APP_IMAGE_TAG="$VCS_REF"
```
Сборка и запуск:
```bash
docker compose config
docker compose build --pull
docker compose up -d --wait
docker compose ps
@@ -76,6 +91,8 @@ trade.flamy.studio {
reverse_proxy 10.20.0.20:18082 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
```
@@ -90,45 +107,89 @@ sudo systemctl reload caddy
## Smoke-check
С docker-runtime:
С Docker-runtime:
```bash
curl -fsS http://10.20.0.20:18082/healthz
curl -I http://10.20.0.20:18082/
curl -fsSI http://10.20.0.20:18082/
curl -fsSI http://10.20.0.20:18082/robots.txt
curl -fsSI http://10.20.0.20:18082/sitemap.xml
```
Снаружи:
```bash
curl -I http://trade.flamy.studio
curl -I https://trade.flamy.studio
curl -fsSI https://trade.flamy.studio/
curl -fsS https://trade.flamy.studio/healthz
curl -fsS https://trade.flamy.studio/robots.txt
curl -fsS https://trade.flamy.studio/sitemap.xml
```
Маршруты:
```text
/ 200
/about 200
/blog 200
/blog/osnovy-upravleniya-riskami 200
/dashboard 200
/healthz 200
/favicon.svg 200
/robots.txt 200
/sitemap.xml 200
/nesuschestvuyuschiy-url 404
/ 200
/about 200
/blog 200
/blog/osnovy-upravleniya-riskami 200
/dashboard 200
/healthz 200
/favicon.svg 200
/robots.txt 200
/sitemap.xml 200
/nesuschestvuyuschiy-url 404
```
Проверка headers:
```bash
curl -fsSI https://trade.flamy.studio/
curl -fsSI https://trade.flamy.studio/favicon.svg
curl -fsSI https://trade.flamy.studio/_app/immutable/<real-file>.js
```
Ожидаемо:
```text
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
```
`Content-Security-Policy` задаётся SvelteKit и ожидается на HTML-ответах приложения.
Для `/_app/immutable/*` ожидается:
```text
Cache-Control: public, max-age=31536000, immutable
```
Для HTML:
```text
Cache-Control: no-cache
```
## Rollback
1. Каждый image получает тег git SHA через `APP_IMAGE_TAG`.
2. Предыдущий image не удаляется до успешного smoke-check.
3. Для отката поменяйте `APP_IMAGE_TAG` на предыдущий SHA.
4. Выполните:
Надёжный rollback должен опираться на immutable image tag:
```bash
docker compose up -d --no-build
```text
${APP_IMAGE_REPOSITORY}:<git-sha>
```
Если проблема в маршрутизации, восстановите backup Caddyfile и выполните `sudo systemctl reload caddy`.
На production-сервере меняется только `APP_IMAGE_TAG`, затем выполняется:
```bash
docker compose pull app
docker compose up -d --no-build --wait
```
Если проблема в маршрутизации, восстановите backup Caddyfile и выполните:
```bash
sudo systemctl reload caddy
```
Предыдущий image не удаляется до успешного smoke-check нового релиза.