diff --git a/README.md b/README.md
index 5242664..7c13d83 100644
--- a/README.md
+++ b/README.md
@@ -30,11 +30,12 @@
2. [Что понадобится](#req)
3. [Самый простой способ: готовый образ](#quick)
4. [Запуск через Docker Compose](#compose)
-5. [Сборка образа у себя](#build-local)
-6. [Сохранение данных и резервная копия](#data)
-7. [Другой порт и доступ по сети](#port)
-8. [Обновление программы](#update)
-9. [Для разработчиков](#dev)
+5. [Production: Traefik + Cloudflare HTTPS](#traefik)
+6. [Сборка образа у себя](#build-local)
+7. [Сохранение данных и резервная копия](#data)
+8. [Другой порт и доступ по сети](#port)
+9. [Обновление программы](#update)
+10. [Для разработчиков](#dev)
---
@@ -166,6 +167,30 @@ docker compose down
---
+
+
+## Production: Traefik + Cloudflare HTTPS (Linux)
+
+На VPS с доменом в Cloudflare: Traefik (Let's Encrypt **DNS-01**) + контейнер VPS Tracker за HTTPS, без публикации порта `3001` наружу.
+
+Пошаговая инструкция, compose и `.env`: **[docs/deploy-traefik.md](docs/deploy-traefik.md)**
+Файлы: [`deploy/docker-compose.traefik.yml`](deploy/docker-compose.traefik.yml), [`deploy/env.traefik.example`](deploy/env.traefik.example).
+
+Кратко:
+
+```bash
+mkdir -p /opt/vps-tracker/data && cd /opt/vps-tracker
+curl -fsSL -o docker-compose.yml \
+ https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/docker-compose.traefik.yml
+curl -fsSL -o .env \
+ https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/env.traefik.example
+nano .env # CF_DNS_API_TOKEN, LETSENCRYPT_EMAIL, VPS_DOMAIN
+docker compose pull && docker compose up -d
+curl -fsS https://ВАШ_ДОМЕН/health
+```
+
+---
+
## Сборка образа у себя
diff --git a/deploy/docker-compose.traefik.yml b/deploy/docker-compose.traefik.yml
new file mode 100644
index 0000000..2fd34b3
--- /dev/null
+++ b/deploy/docker-compose.traefik.yml
@@ -0,0 +1,103 @@
+# VPS Tracker + Traefik in one Compose stack (production, Linux).
+# Docs: docs/deploy-traefik.md
+#
+# On server:
+# mkdir -p /opt/vps-tracker/data
+# cp deploy/docker-compose.traefik.yml /opt/vps-tracker/docker-compose.yml
+# cp deploy/env.traefik.example /opt/vps-tracker/.env # fill secrets
+# docker login ghcr.io # or git.shts.su if using private registry
+# cd /opt/vps-tracker && docker compose pull && docker compose up -d
+
+services:
+ traefik:
+ image: traefik:${TRAEFIK_IMAGE_TAG:-v3.7}
+ container_name: vps-tracker-traefik
+ restart: unless-stopped
+ security_opt:
+ - no-new-privileges:true
+ ports:
+ - "${TRAEFIK_HTTP_PORT:-80}:80"
+ - "${TRAEFIK_HTTPS_PORT:-443}:443"
+ environment:
+ CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN:?set CF_DNS_API_TOKEN in .env}
+ # Optional if DNS token lacks Zone:Read:
+ # CF_ZONE_API_TOKEN: ${CF_ZONE_API_TOKEN:-}
+ command:
+ - --log.level=${TRAEFIK_LOG_LEVEL:-INFO}
+ - --api.dashboard=false
+ - --providers.docker=true
+ - --providers.docker.exposedbydefault=false
+ - --providers.docker.network=vps-tracker
+ - --entrypoints.web.address=:80
+ - --entrypoints.websecure.address=:443
+ - --entrypoints.web.http.redirections.entrypoint.to=websecure
+ - --entrypoints.web.http.redirections.entrypoint.scheme=https
+ - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL:?set LETSENCRYPT_EMAIL in .env}
+ - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
+ - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
+ - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
+ - --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ - traefik_letsencrypt:/letsencrypt
+ networks:
+ - vps-tracker
+ logging:
+ driver: json-file
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ app:
+ image: ${VPS_TRACKER_IMAGE:-ghcr.io/denozordec/vps-tracker}:${VPS_TRACKER_IMAGE_TAG:-latest}
+ pull_policy: always
+ container_name: vps-tracker
+ restart: unless-stopped
+ depends_on:
+ - traefik
+ # No host ports — only Traefik publishes 80/443.
+ environment:
+ PORT: "3001"
+ RUNTIME: "fastify"
+ NODE_ENV: production
+ AUTH_REQUIRED: ${AUTH_REQUIRED:-false}
+ AUTH_JWT_SECRET: ${AUTH_JWT_SECRET:-}
+ AUTH_ISSUER: ${AUTH_ISSUER:-https://auth.shnt.top}
+ AUTH_PORTAL_URL: ${AUTH_PORTAL_URL:-https://auth.shnt.top}
+ volumes:
+ - ./data:/app/data
+ networks:
+ - vps-tracker
+ labels:
+ - traefik.enable=true
+ - traefik.docker.network=vps-tracker
+ - traefik.http.routers.vps-tracker.rule=Host(`${VPS_DOMAIN:-vps.shnt.top}`)
+ - traefik.http.routers.vps-tracker.entrypoints=websecure
+ - traefik.http.routers.vps-tracker.tls=true
+ - traefik.http.routers.vps-tracker.tls.certresolver=letsencrypt
+ - traefik.http.services.vps-tracker.loadbalancer.server.port=3001
+ healthcheck:
+ test:
+ [
+ "CMD",
+ "node",
+ "-e",
+ "fetch('http://127.0.0.1:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
+ ]
+ interval: 30s
+ timeout: 5s
+ retries: 3
+ start_period: 20s
+ logging:
+ driver: json-file
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+volumes:
+ traefik_letsencrypt:
+ name: vps_tracker_traefik_letsencrypt
+
+networks:
+ vps-tracker:
+ name: vps-tracker
diff --git a/deploy/env.traefik.example b/deploy/env.traefik.example
new file mode 100644
index 0000000..35715bb
--- /dev/null
+++ b/deploy/env.traefik.example
@@ -0,0 +1,23 @@
+# Production .env for deploy/docker-compose.traefik.yml (VPS Tracker + Traefik).
+# Copy to /opt/vps-tracker/.env and fill secrets. Do not commit.
+
+# --- Traefik / Let's Encrypt (Cloudflare DNS-01) ---
+CF_DNS_API_TOKEN=
+LETSENCRYPT_EMAIL=admin@example.com
+VPS_DOMAIN=vps.example.com
+# TRAEFIK_IMAGE_TAG=v3.7
+# TRAEFIK_HTTP_PORT=80
+# TRAEFIK_HTTPS_PORT=443
+# TRAEFIK_LOG_LEVEL=INFO
+
+# --- Image ---
+# Default: GHCR. For Gitea registry, e.g.:
+# VPS_TRACKER_IMAGE=git.shts.su/denozord/vps-tracker
+VPS_TRACKER_IMAGE=ghcr.io/denozordec/vps-tracker
+VPS_TRACKER_IMAGE_TAG=latest
+
+# --- Portal SSO (optional; leave AUTH_REQUIRED=false for local API token mode) ---
+AUTH_REQUIRED=false
+AUTH_JWT_SECRET=
+AUTH_ISSUER=https://auth.shnt.top
+AUTH_PORTAL_URL=https://auth.shnt.top
diff --git a/docs/deploy-traefik.md b/docs/deploy-traefik.md
new file mode 100644
index 0000000..dae1c5a
--- /dev/null
+++ b/docs/deploy-traefik.md
@@ -0,0 +1,211 @@
+# Развёртывание VPS Tracker + Traefik (Linux, HTTPS, Cloudflare DNS-01)
+
+Один стек Docker Compose: **Traefik** (HTTPS, Let's Encrypt через Cloudflare DNS challenge) + **vps-tracker** (API + SPA, SQLite).
+
+| | |
+|---|---|
+| Образ | `ghcr.io/denozordec/vps-tracker` (или свой registry через `VPS_TRACKER_IMAGE`) |
+| Пример URL | `https://vps.example.com` |
+| Compose | [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml) |
+| Env | [`deploy/env.traefik.example`](../deploy/env.traefik.example) |
+
+Документация Traefik: [Expose Docker](https://doc.traefik.io/traefik/expose/docker/basic/), [ACME DNS challenge](https://doc.traefik.io/traefik/https/acme/), [Cloudflare provider](https://doc.traefik.io/traefik/https/acme/#providers).
+
+```
+Internet → :80/:443 (Traefik) → vps-tracker:3001
+ ↑
+ Cloudflare DNS TXT (_acme-challenge) для Let's Encrypt
+```
+
+## Предпосылки
+
+1. Зона домена в **Cloudflare**.
+2. **Linux** VPS с **Docker Engine** + плагин **Compose**.
+3. Свободные порты **80** и **443** на хосте (этот стек сам поднимает Traefik).
+4. Доступ к registry образа (`docker login ghcr.io` или `git.shts.su`).
+
+> Если на сервере уже крутится другой Traefik/nginx на 80/443 — остановите его или смените `TRAEFIK_HTTP_PORT` / `TRAEFIK_HTTPS_PORT`. Два процесса на одних портах не запустятся.
+
+---
+
+## 1. Cloudflare: токен и DNS
+
+### API-токен для ACME
+
+[API Tokens → Create Token](https://dash.cloudflare.com/profile/api-tokens) — шаблон **Edit zone DNS** или Custom:
+
+| Permission | Access |
+|------------|--------|
+| Zone → DNS | **Edit** |
+| Zone → Zone | Read (желательно) |
+
+**Zone Resources:** Include → Specific zone → ваша зона.
+
+Значение токена → `CF_DNS_API_TOKEN` в `.env`. Не Global API Key, не коммитьте в Git.
+
+### DNS-запись хоста
+
+| Type | Name | Content | Proxy |
+|------|------|---------|-------|
+| `A` / `AAAA` | `vps` (или нужный поддомен) | IP вашего VPS | **DNS only** (серое облако) |
+
+```bash
+dig +short vps.example.com A
+```
+
+Traefik для сертификата создаёт TXT `_acme-challenge.…` через Cloudflare API. HTTP-01 на публичный :80 для выдачи сертификата **не обязателен** (удобно при firewall / CDN).
+
+---
+
+## 2. Файлы на сервере
+
+```bash
+mkdir -p /opt/vps-tracker/data
+cd /opt/vps-tracker
+
+# Вариант A — из клона репозитория:
+# cp deploy/docker-compose.traefik.yml docker-compose.yml
+# cp deploy/env.traefik.example .env
+
+# Вариант B — скачать с GitHub:
+curl -fsSL -o docker-compose.yml \
+ https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/docker-compose.traefik.yml
+curl -fsSL -o .env \
+ https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/env.traefik.example
+
+nano .env # заполнить секреты и домен
+```
+
+### Обязательные переменные в `.env`
+
+| Переменная | Назначение |
+|------------|------------|
+| `CF_DNS_API_TOKEN` | Cloudflare token для ACME DNS-01 (env контейнера **Traefik**) |
+| `LETSENCRYPT_EMAIL` | Email для Let's Encrypt |
+| `VPS_DOMAIN` | Хост в Traefik `Host(…)` (например `vps.example.com`) |
+
+Опционально:
+
+| Переменная | Назначение |
+|------------|------------|
+| `VPS_TRACKER_IMAGE` / `VPS_TRACKER_IMAGE_TAG` | Образ и тег |
+| `TRAEFIK_IMAGE_TAG`, `TRAEFIK_HTTP_PORT`, `TRAEFIK_HTTPS_PORT` | Версия Traefik и порты |
+| `AUTH_REQUIRED`, `AUTH_JWT_SECRET`, `AUTH_ISSUER`, `AUTH_PORTAL_URL` | SSO через auth-portal |
+
+Для SSO с auth-portal:
+
+```env
+AUTH_REQUIRED=true
+AUTH_JWT_SECRET=<тот же JWT_SECRET что на портале>
+AUTH_ISSUER=https://auth.shnt.top
+AUTH_PORTAL_URL=https://auth.shnt.top
+```
+
+На стороне портала `VPS_DOMAIN` должен быть в `RETURN_TO_ALLOWLIST` (см. [auth-portal deploy-traefik](https://git.shts.su/denozord/auth-portal/src/branch/main/docs/deploy-traefik.md)).
+
+---
+
+## 3. Запуск
+
+```bash
+cd /opt/vps-tracker
+
+# GHCR (публичный / с PAT):
+docker login ghcr.io
+# или приватный Gitea:
+# docker login git.shts.su
+
+docker compose pull
+docker compose up -d
+docker compose ps
+docker compose logs -f --tail=100
+```
+
+Сервисы:
+
+| Service | Контейнер | Роль |
+|---------|-----------|------|
+| `traefik` | `vps-tracker-traefik` | :80 → HTTPS, ACME DNS-01, роутинг |
+| `app` | `vps-tracker` | приложение на сети `vps-tracker`, порт **3001** |
+
+Первая выдача сертификата обычно 30–90 с.
+
+### Проверка
+
+```bash
+curl -fsS https://vps.example.com/health
+# {"ok":true}
+
+echo | openssl s_client -connect vps.example.com:443 -servername vps.example.com 2>/dev/null \
+ | openssl x509 -noout -issuer -dates -subject
+
+docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
+```
+
+Откройте в браузере `https://vps.example.com`.
+
+### Обновление / остановка
+
+```bash
+cd /opt/vps-tracker
+docker compose pull
+docker compose up -d
+
+# остановка (тома и ./data сохраняются)
+docker compose down
+
+# НЕ делайте down -v без бэкапа — сотрёт ACME (том vps_tracker_traefik_letsencrypt)
+```
+
+Откат образа: в `.env` `VPS_TRACKER_IMAGE_TAG=` → `docker compose pull && docker compose up -d`.
+
+---
+
+## Что внутри compose
+
+- Сеть Docker **`vps-tracker`** (внутренняя).
+- Том **`vps_tracker_traefik_letsencrypt`** → `/letsencrypt/acme.json`.
+- Том хоста **`./data`** → SQLite VPS Tracker (`/app/data` в контейнере).
+- Labels на `app`: `Host(VPS_DOMAIN)`, `entrypoints=websecure`, `certresolver=letsencrypt`, backend port `3001`.
+- Порт приложения **не** публикуется на хост — снаружи только Traefik `:80` / `:443`.
+- `CF_DNS_API_TOKEN` только у сервиса `traefik`.
+
+Полный файл: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml).
+
+---
+
+## Бэкап
+
+```bash
+# SQLite
+cp /opt/vps-tracker/data/vps-tracker.db \
+ /opt/vps-tracker/data/vps-tracker.db.bak-$(date +%F)
+
+# ACME (Let's Encrypt)
+docker run --rm -v vps_tracker_traefik_letsencrypt:/data -v "$PWD:/backup" alpine \
+ tar czf /backup/traefik-acme-$(date +%F).tgz -C /data .
+```
+
+---
+
+## Troubleshooting
+
+| Симптом | Что проверить |
+|---------|----------------|
+| `Bind for 0.0.0.0:80/443 failed` | Другой Traefik/nginx занимает порты |
+| ACME / браузер ругается на сертификат | `CF_DNS_API_TOKEN`, Zone:DNS:Edit, DNS only (не orange cloud), логи Traefik |
+| `invalid credentials` | Не Global Key; зона в scope токена; нет пробелов/кавычек в `.env` |
+| Gateway Timeout / 404 | `docker compose ps`; labels; сеть `vps-tracker`; `VPS_DOMAIN` |
+| `/health` OK, UI пустой | образ / кэш CDN; смотрите `docker compose logs app` |
+| SSO 401 / issuer mismatch | `AUTH_JWT_SECRET` = `JWT_SECRET` портала; `AUTH_ISSUER` |
+
+```bash
+docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
+docker compose logs app --tail=80
+```
+
+---
+
+## Простой compose без Traefik
+
+Локально / за своим reverse-proxy: корневой [`docker-compose.yml`](../docker-compose.yml) с пробросом `3001:3001` — см. [README](../README.md#compose).