Legan Studio
Все статьи
~ 16 мин чтения

Деплой бота в MAX в продакшен: инфраструктура и CI/CD

Как развернуть бота MAX в продакшен: контейнеризация, CI/CD, нулевой даунтайм, миграции БД, секреты, hosting в РФ.

  • MAX
  • разработка
  • инфраструктура

Между «бот работает у меня на ноуте» и «бот в проде» — большой технический разрыв. В этом разрыве — Docker, CI/CD, миграции БД, секреты, мониторинг и нулевой даунтайм при релизах. MAX — российский мессенджер от VK Tech, поэтому требования к инфраструктуре жёстче, чем для зарубежных платформ: 152-ФЗ требует первичной обработки персональных данных на территории РФ, а блокировки зарубежных хостингов делают российские площадки и юридически, и операционно проще. Разберём типовой стек для бота MAX, который выдерживает реальную аудиторию и обновления раз в неделю.

Выбор VPS-провайдера для РФ

Критерии выбора: цена за конфигурацию 2 vCPU / 4 GB RAM (типовой бот среднего размера), надёжность инфраструктуры, география дата-центров, качество техподдержки и наличие managed-сервисов рядом. Для MAX-бота, обрабатывающего ПДн пользователей VK Tech, важно соблюдение 152-ФЗ — первичная обработка должна вестись в России.

ПровайдерЦена 2 vCPU / 4 GBDCПоддержкаОсобенности
Yandex Cloudот 1500 руб/месМосква, Владимир, Калуга24/7, тикетыManaged PostgreSQL/Redis, S3, YandexGPT
Selectelот 800 руб/месСПб, Москва24/7, чат и тикетыГибкие конфиги, S3-хранилище, bare metal
VK Cloudот 1300 руб/месМосква24/7Естественный выбор для MAX (одна экосистема VK Tech)
REG.RUот 600 руб/месМосква, СПбБазоваяПростой VPS, домены рядом
Begetот 700 руб/месМоскваЧат, отзывчиваяДружелюбный для новичка
FirstVDSот 500 руб/месМосква, СПбТикетыСамый дешёвый сегмент
Timeweb Cloudот 700 руб/месСПб, Москва24/7Хороший баланс цена/качество

Для MAX-проектов естественный выбор — VK Cloud: одна экосистема с самим мессенджером, общий биллинг, общая поддержка. Если важны managed-сервисы и интеграция с YandexGPT — Yandex Cloud. Бюджетный вариант — Beget или FirstVDS.

Какой дата-центр выбрать

MAX — российский мессенджер, его API расположен в России. VPS в Москве или СПб даёт RTT 5–15 мс до серверов MAX, что заметно меньше, чем 30–50 мс из Амстердама. Для webhook-бота это означает быстрее доставку обновлений и меньше задержку в диалоге.

Юридически — DC в РФ обязателен для большинства MAX-ботов. 152-ФЗ требует, чтобы первичная база с ПДн пользователей-россиян физически находилась в России. Для MAX это особенно критично: целевая аудитория мессенджера — почти полностью граждане РФ, и Роскомнадзор обращает на такие проекты пристальное внимание.

Итог: для любого MAX-бота, кроме чисто технологического (без сохранения ПДн), — российский DC, желательно в Москве или СПб для минимизации RTT до API.

Минимальные требования по железу

Размер VPS зависит от профиля нагрузки. Грубые ориентиры:

Тип ботаvCPURAMДискЦена
Маленький (до 1k DAU, без БД)11 GB20 GB500-800 руб/мес
Средний (1-10k DAU, Postgres + Redis)24 GB40 GB1500-2500 руб/мес
С AI/LLM или Mini App48 GB80 GB4000-8000 руб/мес
Высоконагруженный (50k+ DAU, кластер)8+16+ GB160+ GBот 10000 руб/мес

Если RAM меньше 2 GB — обязательно настройте swap-файл на 2 GB, иначе сборка Docker-образа упадёт с OOM. Диск только SSD/NVMe: Postgres на HDD превращает любой запрос в боль.

Выбор операционной системы

Стандарт — Ubuntu 22.04 LTS или 24.04 LTS. Долгий цикл поддержки, обширная документация, все Docker-образы тестируются именно на Ubuntu. Альтернатива — Debian 12 (стабильнее, чуть консервативнее в версиях пакетов). CentOS Stream и Rocky Linux подходят, если у компании корпоративный стандарт RHEL-подобных систем.

Не берите экзотику вроде Arch, Gentoo или FreeBSD для прода — времени на их сопровождение уйдёт больше, чем на собственно бота. Для быстрого старта Ubuntu 24.04 LTS — оптимальный выбор: свежие версии Docker и systemd, поддержка до 2029 года.

Первичная настройка сервера

Сразу после получения root-доступа — обязательный набор шагов, без которых сервер уязвим в течение часов. Подключаемся по SSH под root:

ssh root@1.2.3.4
apt update && apt upgrade -y
apt install -y curl ufw fail2ban htop unattended-upgrades

# Создаём non-root пользователя с sudo
adduser deploy
usermod -aG sudo deploy

# Кладём ваш публичный ключ
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Теперь правим /etc/ssh/sshd_config:

Port 22022
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers deploy

Перезапуск SSH: systemctl restart sshd. Перед закрытием root-сессии — проверьте новое подключение в отдельном окне терминала. Если что-то пошло не так, root-сессия спасёт.

Нестандартный порт SSH (22022 вместо 22) сокращает шумовой трафик от ботов-сканеров на 99% — в логах остаются только адресные атаки.

UFW и fail2ban

Файрвол — обязательный минимум. Открываем только нужное:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22022/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP for certbot'
ufw allow 443/tcp comment 'HTTPS webhook'
ufw enable
ufw status verbose

Fail2ban читает логи SSH и блокирует IP после нескольких неудачных попыток. Дефолтная конфигурация подходит сразу, проверьте её живость через fail2ban-client status sshd. Для nginx тоже есть jail — включите его после установки nginx, чтобы блокировать массовые 404 и 401.

Установка Docker и Docker Compose

Используем официальный репозиторий Docker, не пакет из Ubuntu (он часто отстаёт):

curl -fsSL https://get.docker.com | sh
usermod -aG docker deploy
systemctl enable --now docker

# Проверка
docker --version
docker compose version

Docker Compose v2 идёт как плагин (docker compose, не docker-compose). Старая команда docker-compose работает только если поставить пакет отдельно — не нужно.

Структура проекта

Канонический раскладка для бота с базой и кэшем:

/home/deploy/maxbot/
├── docker-compose.yml
├── .env                  # секреты, не в git
├── nginx/
│   └── conf.d/
│       └── bot.conf
├── postgres/
│   └── data/             # volume
├── redis/
│   └── data/             # volume
└── bot/
    ├── Dockerfile
    ├── requirements.txt
    └── src/

Папка проекта в /home/deploy/, владелец — deploy:deploy, права 755 на директории и 600 на .env. Никогда не кладите проект в /root/ или в домашний каталог root — права и SELinux-контексты ломаются.

docker-compose.yml для четырёх сервисов

Базовый стек: бот, PostgreSQL, Redis, nginx как reverse-proxy:

services:
  bot:
    build:
      context: ./bot
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - internal
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: maxbot
      POSTGRES_USER: maxbot
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres/data:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U maxbot"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - ./redis/data:/data
    networks:
      - internal

  nginx:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
      - ./nginx/logs:/var/log/nginx
    depends_on:
      - bot
    networks:
      - internal

networks:
  internal:
    driver: bridge

PostgreSQL и Redis наружу не публикуем — они доступны только по внутренней сети internal. Бот тоже не публикуется напрямую: вход через nginx на 443.

Dockerfile для Python-бота

Multi-stage build с финальным distroless-образом сокращает размер с 800 МБ до 150 МБ и убирает почти всю поверхность атаки:

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
COPY --from=builder /root/.local /home/nonroot/.local
COPY --chown=nonroot:nonroot src/ ./src/
ENV PYTHONPATH=/app/src
ENV PATH=/home/nonroot/.local/bin:$PATH
USER nonroot
EXPOSE 8080
CMD ["src/main.py"]

USER nonroot обязателен: даже если уязвимость в боте позволит выполнить код, она не получит root внутри контейнера. EXPOSE 8080 — не обязателен для работы, но даёт документацию для тех, кто читает Dockerfile.

Для Go-бота финальный образ может быть ещё меньше — gcr.io/distroless/static весит 2 МБ, статически слинкованный Go-бинарник — 15–30 МБ.

Nginx как reverse-proxy

Конфиг для домена bot.example.ru, проксирующий webhook MAX на внутренний bot:8080:

server {
    listen 80;
    server_name bot.example.ru;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name bot.example.ru;

    ssl_certificate /etc/letsencrypt/live/bot.example.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/bot.example.ru/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;

    limit_req_zone $binary_remote_addr zone=webhook:10m rate=20r/s;

    location /webhook/ {
        limit_req zone=webhook burst=40 nodelay;
        proxy_pass http://bot:8080/webhook/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 30s;
    }

    location / {
        return 404;
    }
}

limit_req спасает от потока поддельных webhook-запросов: 20 RPS в нормальном режиме, до 40 в пике. Реальный MAX отправляет апдейты заметно реже.

Let's Encrypt и certbot

Бесплатный TLS, обновляемый автоматически. На свежем сервере:

apt install -y certbot
mkdir -p /var/www/certbot

# Останавливаем nginx-контейнер на время первичной выдачи
docker compose stop nginx

certbot certonly --standalone \
  -d bot.example.ru \
  --email admin@example.ru \
  --agree-tos --no-eff-email

docker compose up -d nginx

Auto-renewal через systemd-таймер certbot.timer уже включён по умолчанию. Проверить можно через systemctl list-timers | grep certbot. Для перезагрузки nginx после обновления сертификата кладём хук в /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh:

#!/bin/sh
docker compose -f /home/deploy/maxbot/docker-compose.yml exec nginx nginx -s reload

И chmod +x ему. Без хука сертификат обновится, но nginx продолжит отдавать старый до ручной перезагрузки.

Domain и webhook setup

A-record поддомена bot.example.ru направляем на IP VPS у регистратора домена (REG.RU, Beget, RU-CENTER). DNS-пропагация занимает от 5 минут до часа.

После получения TLS-сертификата регистрируем webhook в MAX API. Запрос к setWebhook с HTTPS-URL и обязательным secret_token (32+ случайных символа), который MAX будет передавать в заголовке каждого апдейта:

curl -X POST "https://botapi.max.ru/bot$MAX_TOKEN/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://bot.example.ru/webhook/",
    "secret_token": "RANDOM_32_CHARS_HERE",
    "max_connections": 40
  }'

В коде бота проверяем заголовок X-MAX-Bot-Api-Secret-Token на каждом запросе — если не совпал с ожидаемым, отвечаем 403. Без этой проверки злоумышленник, узнавший URL вашего webhook, сможет слать поддельные апдейты.

Environment и .env

Все секреты — в .env рядом с docker-compose.yml, никогда в git:

MAX_TOKEN=123456:AAAA-real-token-here
MAX_WEBHOOK_SECRET=random_32_chars_here
POSTGRES_PASSWORD=strong_random_password
REDIS_PASSWORD=another_strong_random
DATABASE_URL=postgresql://maxbot:strong_random_password@postgres:5432/maxbot
REDIS_URL=redis://:another_strong_random@redis:6379/0
SENTRY_DSN=https://...@sentry.io/...
LOG_LEVEL=INFO

Права: chmod 600 .env, владелец deploy:deploy. В .gitignore обязательно строка .env. Шаблон .env.example с пустыми значениями — в git, чтобы новый разработчик понял структуру.

PostgreSQL: в Docker или managed

Для бота до 10k DAU PostgreSQL в Docker — нормальное решение. Volume на быстром диске, регулярные pg_dump в S3, и работает годами. Плюсы: всё в одном compose, легко локально воспроизвести.

Для серьёзных проектов — Yandex Managed PostgreSQL или Selectel Cloud Database. Плюсы: автоматические бэкапы с PITR, репликация, мониторинг, обновление мажорных версий в один клик. Минус: дороже примерно в 2–3 раза, чем тот же ресурс на VPS.

Граница примерно такая: пока БД помещается в RAM сервера и обновлений мало — Docker. Как только данных больше 10–20 ГБ или нужна реплика — managed.

Redis в Docker

Redis обычно остаётся в Docker даже у крупных проектов: он легковесный, перезапускается за секунду, потеря данных при сбое не катастрофа (FSM можно пересобрать). Включаем appendonly yes для базовой персистентности и пароль через requirepass — даже во внутренней сети это страховка от случайной публикации порта.

Volumes и персистентность

Данные PostgreSQL и Redis — только на bind mount, не в named volume:

volumes:
  - ./postgres/data:/var/lib/postgresql/data
  - ./redis/data:/data

Bind mount позволяет видеть содержимое в файловой системе хоста, делать tar-бэкап без поднятия контейнера, переносить на другой сервер обычным rsync. Named volume хранится в /var/lib/docker/volumes/, и при удалении compose-проекта легко удалить случайно.

Логи

По умолчанию Docker пишет логи json-file драйвером без ротации, и через месяц они съедят весь диск. Добавляем ротацию глобально в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

После — systemctl restart docker. Теперь каждый контейнер хранит максимум 250 МБ логов.

Для серьёзных проектов — Loki + Promtail + Grafana. Все логи в одном месте, поиск по полям, retention настраивается. Альтернатива — Vector + ClickHouse для очень больших объёмов.

Healthcheck в docker-compose

Healthcheck — не косметика. Без него Docker не знает, жив ли контейнер, и restart: unless-stopped не сработает на «висячий» процесс. Минимум для бота:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 20s

В коде бота — отдельный /health эндпоинт, который возвращает 200 если БД и Redis отвечают, 503 если нет. Тогда Docker перезапустит зависший контейнер автоматически, и в depends_on других сервисов можно использовать condition: service_healthy.

CI/CD через GitHub Actions

Минимальный пайплайн: на push в main собираем образ, пушим в registry, через SSH тянем на сервер и перезапускаем compose:

name: Deploy MAX bot

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -p 22022 -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts

      - name: Sync code
        run: |
          rsync -avz --delete \
            --exclude '.git' --exclude '.env' --exclude 'postgres/data' --exclude 'redis/data' \
            -e "ssh -p 22022 -i ~/.ssh/id_ed25519" \
            ./ deploy@${{ secrets.SERVER_HOST }}:/home/deploy/maxbot/

      - name: Rebuild and restart
        run: |
          ssh -p 22022 -i ~/.ssh/id_ed25519 deploy@${{ secrets.SERVER_HOST }} \
            "cd /home/deploy/maxbot && docker compose build bot && docker compose up -d bot"

Секреты DEPLOY_KEY и SERVER_HOST лежат в GitHub Secrets. Ключ генерируется одноразово, добавляется в ~/.ssh/authorized_keys пользователя deploy на сервере.

Bluegreen и rolling deploy

Простой compose не делает rolling из коробки. Способы добиться нулевого даунтайма:

  • Двухкопийная схема через nginx — поднимаем bot-v2, nginx проксирует на оба, потом на v1 ставим weight=0 и удаляем.
  • Docker Swarm — нативный rolling update через docker service update --update-parallelism 1.
  • Kubernetes — родной механизм rolling deployment, но overkill для одного бота.

Для бота на webhook чаще всего хватает graceful shutdown: при SIGTERM завершаем обработку текущих апдейтов и выходим. Потеря 2–3 секунд апдейтов при деплое незаметна — MAX повторит их через webhook retry.

Мониторинг: Prometheus + Grafana

Минимальный стек для наблюдаемости — Prometheus (сборщик метрик), Node Exporter (метрики хоста), cAdvisor (метрики контейнеров), Grafana (визуализация). Добавляем сервисы в тот же compose:

prometheus:
  image: prom/prometheus:latest
  volumes:
    - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
    - ./prometheus/data:/prometheus
  networks: [internal]

node-exporter:
  image: prom/node-exporter:latest
  pid: host
  volumes:
    - /:/host:ro,rslave
  command: ["--path.rootfs=/host"]
  networks: [internal]

cadvisor:
  image: gcr.io/cadvisor/cadvisor:latest
  volumes:
    - /:/rootfs:ro
    - /var/run:/var/run:ro
    - /sys:/sys:ro
    - /var/lib/docker/:/var/lib/docker:ro
  networks: [internal]

grafana:
  image: grafana/grafana:latest
  environment:
    GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
  volumes:
    - ./grafana/data:/var/lib/grafana
  networks: [internal]

В Grafana импортируем готовые дашборды: Node Exporter Full (ID 1860), cAdvisor (ID 14282). За полчаса получаем картину «как живёт сервер и контейнеры». Алерты — в Telegram-чат через Alertmanager.

Бэкапы PostgreSQL в S3

Ежедневный pg_dump в Yandex Object Storage или Selectel S3. Скрипт в /home/deploy/maxbot/backup.sh:

#!/bin/bash
set -euo pipefail

BACKUP_DIR=/home/deploy/backups
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
FILENAME="maxbot_${TIMESTAMP}.sql.gz"

mkdir -p "$BACKUP_DIR"

docker compose -f /home/deploy/maxbot/docker-compose.yml exec -T postgres \
  pg_dump -U maxbot maxbot | gzip > "$BACKUP_DIR/$FILENAME"

# Загрузка в S3
aws s3 cp "$BACKUP_DIR/$FILENAME" \
  "s3://maxbot-backups/postgres/$FILENAME" \
  --endpoint-url=https://storage.yandexcloud.net

# Удаляем локальные копии старше 7 дней
find "$BACKUP_DIR" -name "maxbot_*.sql.gz" -mtime +7 -delete

# В S3 настроен lifecycle: автоудаление через 90 дней
echo "Backup $FILENAME uploaded successfully"

В cron: 0 3 * * * /home/deploy/maxbot/backup.sh >> /var/log/maxbot-backup.log 2>&1 — ежедневно в 3:00. Раз в квартал — тестовое восстановление в staging-окружение, иначе бэкапов словно нет.

Безопасность

Слои защиты складываются:

  • secret_token в webhook — отсекает поддельные апдейты на уровне приложения.
  • IP-allowlist в nginx, если VK Tech опубликует CIDR-диапазоны MAX API — на момент написания статьи официального списка не было, но проверьте свежую документацию.
  • Rate limit в nginx (limit_req) — против любого потока запросов.
  • fail2ban на nginx-логи — блокирует IP с массовыми 401/403/404.
  • UFW — закрывает всё, кроме 22022/80/443.
  • Регулярные apt upgrade через unattended-upgrades — критические патчи безопасности применяются автоматически.
  • Не-root в контейнереUSER nonroot в Dockerfile.
  • Read-only root filesystem где возможно — read_only: true в compose.

Каждый слой ловит разные классы атак. Один-два слоя пропускают, набор — нет.

Стоимость владения

Грубая оценка ежемесячных расходов для типового MAX-бота среднего размера:

СтатьяМаленький ботСредний ботС AI/Mini App
VPS700 руб2000 руб6000 руб
Домен (.ru)15 руб15 руб15 руб
TLS (Let's Encrypt)0 руб0 руб0 руб
S3 для бэкапов50 руб150 руб300 руб
Managed PostgreSQL (опц.)1500 руб3000 руб
Мониторинг (selfhost)0 руб0 руб0 руб
Итого~700 руб2000-3500 руб6000-10000 руб

К этому стоит прибавить время DevOps на сопровождение: 2–4 часа в месяц для маленького бота, 8–16 для среднего, отдельная роль на полставки для крупного. Часто эти часы окупаются единственной аварией, которую успели предотвратить.

Что обычно забывают

  • Лимит ресурсов на контейнер (mem_limit, cpus) — без них утечка памяти убивает соседей.
  • Часовой пояс — внутри контейнера UTC, в логах UTC, отображение пользователю в локальном.
  • Health check для MAX API — проверять можно через getMe, который не требует апдейтов.
  • Тестовый прогон деплоя — staging должен быть похожим на прод по конфигу.
  • Документация — README с инструкцией «как поднять с нуля» спасает через год, когда никто уже не помнит шагов.
  • Алерты — Prometheus без алертов = просто красивые графики.

Итого

Прод-деплой бота в MAX — это сборка из контейнеризации, managed-БД (или хорошо настроенного Postgres в Docker), CI/CD с миграциями, секретов в защищённом хранилище, graceful shutdown и наблюдаемости. Российский DC обязателен по 152-ФЗ, и для MAX это естественно — VK Cloud, Yandex Cloud или Selectel закрывают все потребности. Всё это собирается за пару недель работы DevOps и потом окупается тем, что релизы становятся скучным процессом, а не ночным героизмом.

Частые вопросы

Какой стек нужен для деплоя бота MAX в продакшен?

Стандартный набор. Контейнеризация через Docker (multi-stage build, distroless или alpine, non-root user). Хранилища: PostgreSQL для бизнес-данных, Redis для FSM/кэша/очередей, S3-совместимое (Yandex Object Storage) для вложений. CI/CD: GitHub Actions, GitLab CI или Yandex Cloud Build. Реестр образов: Yandex Container Registry или Harbor. Балансировщик: nginx с TLS от Let's Encrypt. Хостинг: VK Cloud, Yandex Cloud или Selectel — для соответствия 152-ФЗ серверы должны быть в РФ. Мониторинг: Prometheus + Grafana + Node Exporter + cAdvisor. Бэкапы: ежедневный pg_dump в S3 с retention 90 дней.

Где разместить MAX-бота, чтобы соответствовать 152-ФЗ?

На серверах в России. Подходящие площадки: VK Cloud (естественный выбор, одна экосистема с MAX от VK Tech), Yandex Cloud (широкий спектр managed-сервисов, VPC, приватные сети), Selectel (плюс bare metal), любой VPS-провайдер с серверами в РФ для небольших ботов. Аудитория MAX — почти полностью граждане РФ, поэтому Роскомнадзор обращает на такие проекты пристальное внимание. Managed PostgreSQL и Redis от российских провайдеров снимают вопросы локализации ПДн. Российский DC даёт ещё и низкий RTT до API мессенджера: 5–15 мс из Москвы против 30–50 мс из Амстердама.

Как зарегистрировать webhook в MAX API безопасно?

Через метод setWebhook с обязательным secret_token из 32+ случайных символов. URL должен быть HTTPS — в проде это домен с сертификатом Let's Encrypt. Запрос: POST https://botapi.max.ru/bot<TOKEN>/setWebhook с JSON { "url": "https://bot.example.ru/webhook/", "secret_token": "...", "max_connections": 40 }. В коде бота проверяем заголовок X-MAX-Bot-Api-Secret-Token на каждом запросе — если не совпал с ожидаемым, отвечаем 403. Без этой проверки злоумышленник, узнавший URL webhook, сможет слать поддельные апдейты. Дополнительно стоит включить rate limit в nginx (например, 20 RPS с burst 40) и fail2ban на 401/403 в логах.

Как обновлять MAX-бота без потери сообщений?

Через два механизма. Rolling update — оркестратор (Docker Compose, Kubernetes, Swarm) сначала запускает новый контейнер, потом гасит старый. Между ними короткий период, когда оба работают. Graceful shutdown — при SIGTERM бот заканчивает обработку текущих апдейтов и только потом выходит (через context cancellation в Go или signal handler в Python). Для webhook нужен балансировщик nginx перед ботом, который держит соединение, пока новый контейнер запускается. На практике для бота на webhook потеря 2–3 секунд апдейтов при деплое незаметна — MAX повторяет недоставленные апдейты через webhook retry.

Где хранить токены и секреты MAX-бота в проде?

Не в коде, не в Dockerfile, не в docker-compose под git. Четыре рабочих варианта по нарастанию серьёзности: файл .env на сервере с правами 600 и владельцем deploy:deploy (минимальный для маленького проекта), HashiCorp Vault (для серьёзных установок), Yandex Lockbox или VK Cloud Secret Manager (managed-секреты в облаке), Docker secrets и Kubernetes secrets (для оркестраторов). CI получает секреты из защищённого хранилища (GitHub Secrets, Vault), пробрасывает в команду деплоя через переменные окружения. В .gitignore обязательно .env, в репозитории — только шаблон .env.example с пустыми значениями. Утечка токена MAX — критический инцидент, требующий немедленного revokeWebhook и перевыпуска токена через @MasterBot.

Как настроить ежедневные бэкапы PostgreSQL в S3?

Скрипт с pg_dump через docker compose exec, gzip, загрузка в S3 через aws cli с эндпоинтом Yandex Object Storage или Selectel S3. Локальные копии чистим find -mtime +7. На стороне S3 настраиваем lifecycle policy с автоудалением через 90 дней. Запуск через cron: 0 3 * * * /home/deploy/maxbot/backup.sh — ежедневно в 3:00 ночи, когда нагрузка минимальна. Логи бэкапа пишем в /var/log/maxbot-backup.log для отладки. Раз в квартал — обязательное тестовое восстановление в staging: бэкап без проверки восстановления словно его и нет. Для серьёзных проектов поверх pg_dump — WAL-G или pgBackRest с PITR (восстановление на любую секунду в пределах retention).

Какой минимальный CI/CD пайплайн нужен MAX-боту?

Три этапа. На каждый pull request — запуск линтера, прогон тестов, сборка Docker-образа (но не пуш). На merge в main — пуш образа в registry, автоматический выкат на staging-окружение. Ручное подтверждение — выкат на прод. Инструменты: GitHub Actions (самый низкий порог входа), GitLab CI (если у клиента собственный GitLab), Yandex Cloud Build (если уже в их экосистеме). Хранение образов — Yandex Container Registry или собственный Harbor. Для деплоя — SSH-ключ в GitHub Secrets, rsync кода и docker compose up -d. Этого достаточно для 90% проектов; усложнение через Kubernetes имеет смысл от десятков сервисов и нескольких сред.