docker-compose.yml: сервис backup (образ из реестра, явный environment только с BACKUP_*, тома данных + backup-data), предупреждение про down -v. docker-compose.test.yml: тот же образ локальной сборкой, только локальный репозиторий, без расписания, BACKUP_VPS_HOST принудительно пуст. .env.example: новый блок BACKUP_* (пароль, расписание, хранение, сжатие, SFTP-пользователь fsbackup — системный backup в Debian/Ubuntu уже занят, ключ base64, адрес Pi для скриптов ПК); удалены BACKUP_VPS_KEY и BACKUP_KEEP_LOCAL/REMOTE. build-push: сообщения про третий образ (bake подхватывает сервис сам). #64 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
120 lines
7.1 KiB
YAML
120 lines
7.1 KiB
YAML
# ─────────────────────────────────────────────────────────────────────────────
|
||
# ПРОД на Raspberry Pi (forbiddenstars.ru) — единый compose: приложение + туннель.
|
||
# На Pi нужны только ДВА файла: этот docker-compose.yml и .env. Репозиторий, сборка и
|
||
# файл ключа не нужны: образы тянутся из реестра (pull_policy: always), а приватный ключ
|
||
# туннеля лежит в .env (TUNNEL_KEY_B64, base64). build: оставлен для сборки/пуша на ПК
|
||
# (scripts/build-push.*) — на Pi он не используется.
|
||
#
|
||
# Запуск/обновление: docker compose up -d (сам тянет свежие образы)
|
||
# Логи: docker compose logs -f app (или: ... tunnel / backup)
|
||
# Остановка: docker compose down (данные в томах сохраняются)
|
||
# Бэкапы: docker compose exec backup fs-backup status (help — все команды)
|
||
# инструкция: deploy/backup/README.md
|
||
#
|
||
# НИКОГДА не выполняйте на проде `docker compose down -v`: флаг -v удаляет тома — и данные
|
||
# приложения, и локальный репозиторий бэкапов (останется только копия на VPS).
|
||
#
|
||
# Контейнер ВСЕГДА production: APP_ENV форсится здесь и игнорирует значение из .env.
|
||
# Порты на хост НЕ публикуются — наружу приложение выставляет только сервис tunnel
|
||
# (SSH reverse-туннель к VPS). Из LAN/localhost оно недоступно.
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
services:
|
||
app:
|
||
build: .
|
||
# Образ из реестра (Gitea): собирается под arm64 на ПК (scripts/build-push.sh) и тянется
|
||
# на Pi через `docker compose pull`. build: оставлен как локальный фолбэк (сборка на Pi).
|
||
image: ${IMAGE_REGISTRY:-gitea.arseniev.info/notbigghost}/forbidden-stars:${IMAGE_TAG:-latest}
|
||
pull_policy: always # на Pi всегда тянем образ из реестра (без сборки)
|
||
restart: unless-stopped
|
||
init: true # PID 1 пробрасывает сигналы → корректный SIGTERM
|
||
stop_grace_period: 30s # время на чек-пойнт WAL SQLite перед остановкой
|
||
env_file:
|
||
- .env # единый источник настроек/секретов всего приложения
|
||
environment:
|
||
APP_ENV: production # прод обособлен: игнорируем APP_ENV из .env
|
||
volumes:
|
||
- db-data:/data # БД SQLite + WAL-сайдкары
|
||
- uploads-data:/data/uploads # медиа партий
|
||
- achievements-data:/data/achievements # определения титулов (файлы)
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 40s
|
||
mem_limit: ${APP_MEM_LIMIT:-512m} # лимиты под Pi; переопределяются в .env
|
||
cpus: ${APP_CPUS:-1.5}
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "5"
|
||
|
||
# SSH reverse-туннель: VPS:${VPS_TUNNEL_PORT:-9000} → app:8000 (домен forbiddenstars.ru).
|
||
# Приватный ключ — в .env как TUNNEL_KEY_B64 (base64); pubkey — в authorized_keys у tunnel@VPS.
|
||
tunnel:
|
||
build: ./deploy/tunnel
|
||
image: ${IMAGE_REGISTRY:-gitea.arseniev.info/notbigghost}/forbidden-stars-tunnel:${IMAGE_TAG:-latest}
|
||
pull_policy: always
|
||
restart: unless-stopped
|
||
init: true
|
||
depends_on:
|
||
app:
|
||
condition: service_healthy
|
||
environment:
|
||
VPS_TUNNEL_HOST: ${VPS_TUNNEL_HOST} # обязателен; tunnel.sh упадёт с ошибкой, если пуст
|
||
VPS_TUNNEL_USER: ${VPS_TUNNEL_USER:-tunnel}
|
||
VPS_TUNNEL_PORT: ${VPS_TUNNEL_PORT:-9000} # прод-слот (forbiddenstars.ru)
|
||
UPSTREAM: app:8000
|
||
TUNNEL_KEY_B64: ${TUNNEL_KEY_B64:-} # приватный ключ туннеля (base64), из .env
|
||
mem_limit: 64m
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
|
||
# Бэкапы (restic): снимки по расписанию в локальный репозиторий (том backup-data) и на VPS
|
||
# (SFTP, если задан BACKUP_VPS_HOST). От app не зависит и app не мешает. Переменные —
|
||
# только BACKUP_* (секреты приложения сюда не передаются). Всё про настройку и
|
||
# восстановление — deploy/backup/README.md.
|
||
backup:
|
||
build: ./deploy/backup
|
||
image: ${IMAGE_REGISTRY:-gitea.arseniev.info/notbigghost}/forbidden-stars-backup:${IMAGE_TAG:-latest}
|
||
pull_policy: always
|
||
restart: unless-stopped
|
||
environment:
|
||
BACKUP_PASSWORD: ${BACKUP_PASSWORD:-} # пароль шифрования; пусто = бэкапы отключены
|
||
BACKUP_HOSTNAME: fs-prod # имя хоста в снимках (у тест-клона — fs-test)
|
||
BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 4 * * *}
|
||
BACKUP_VERIFY_SCHEDULE: ${BACKUP_VERIFY_SCHEDULE:-30 5 * * 0}
|
||
BACKUP_KEEP_DAILY: ${BACKUP_KEEP_DAILY:-14}
|
||
BACKUP_KEEP_WEEKLY: ${BACKUP_KEEP_WEEKLY:-8}
|
||
BACKUP_KEEP_MONTHLY: ${BACKUP_KEEP_MONTHLY:-12}
|
||
BACKUP_COMPRESSION: ${BACKUP_COMPRESSION:-max}
|
||
BACKUP_MAX_AGE_HOURS: ${BACKUP_MAX_AGE_HOURS:-30}
|
||
BACKUP_VPS_HOST: ${BACKUP_VPS_HOST:-}
|
||
BACKUP_VPS_USER: ${BACKUP_VPS_USER:-fsbackup}
|
||
BACKUP_VPS_PORT: ${BACKUP_VPS_PORT:-22}
|
||
BACKUP_VPS_DIR: ${BACKUP_VPS_DIR:-/srv/fs-backups/restic}
|
||
BACKUP_SSH_KEY_B64: ${BACKUP_SSH_KEY_B64:-}
|
||
TZ: ${BACKUP_TZ:-Europe/Moscow}
|
||
volumes:
|
||
- db-data:/fs-db # живая БД (снимается консистентно)
|
||
- uploads-data:/fs/uploads
|
||
- achievements-data:/fs/achievements
|
||
- backup-data:/backup # локальный репозиторий restic + состояние
|
||
mem_limit: ${BACKUP_MEM_LIMIT:-384m}
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
volumes:
|
||
db-data:
|
||
uploads-data:
|
||
achievements-data:
|
||
backup-data:
|