Деплой на сервер (VPS) и Docker

Учимся переселять «Цыплёнка-помощника» с твоего ноутбука на настоящий сервер, чтобы он отвечал друзьям даже ночью и когда ты на учёбе.
Деплой — это перенос готовой программы туда, где она будет работать постоянно: на сервер, который не выключается, не уходит в спящий режим и не теряет интернет, когда ты закрываешь крышку ноутбука.

Зачем вообще куда-то переезжать

Представь: ты собрал крутого бота-напоминалку про домашку, скинул ссылку друзьям из класса, все подписались. А вечером ты закрыл ноутбук, лёг спать — и бот замолчал. Утром Маша пишет «/start», а в ответ тишина: твоя программа ведь жила на твоём компьютере, а компьютер спит вместе с тобой.

Пока ты учился писать бота, запускать его прямо на своей машине было нормально. Но как только ботом начинают пользоваться другие люди, ему нужен дом, который никогда не закрывается. Такой дом называется VPS — это виртуальный приватный сервер, по сути небольшой компьютер где-то в дата-центре, который работает 24 часа в сутки 7 дней в неделю и всегда подключён к интернету. Ты арендуешь его за пару сотен рублей в месяц, заходишь по сети и запускаешь там своего «Цыплёнка».

К концу этого урока твой бот будет жить на сервере и переживать любые перезагрузки — даже если сервер внезапно выключат и включат, бот поднимется сам. А ещё мы научимся упаковывать его в Docker, чтобы переезжать между серверами было так же просто, как перенести один файл.

VPS — это просто чужой компьютер, к которому ты ходишь по сети

Не пугайся слова «сервер». Сервер — это обычный компьютер, только без монитора и клавиатуры рядом с тобой. Ты управляешь им через текстовую консоль по протоколу SSH: набираешь команды, нажимаешь Enter, видишь ответ. Это как переписка в мессенджере, только собеседник — операционная система Linux.

Когда ты арендуешь VPS (например, у российского хостера), тебе дают три вещи: IP-адрес сервера (что-то вроде 185.22.10.7), имя пользователя (обычно root) и пароль. С ними ты подключаешься так:

ssh [email protected]

Результат: сервер спросит пароль (символы при вводе не отображаются — это нормально), и ты окажешься внутри: в строке появится приглашение вроде root@vps:~#. Поздравляю, ты в гостях у своего сервера.

Шаг 1. Ставим Python и инструменты

На свежем сервере часто нет ни Python нужной версии, ни менеджера пакетов pip, ни git. Установим всё одним заходом. Сначала обновим список доступных программ, потом доставим нужное:

apt update
apt install -y python3 python3-pip python3-venv git

Результат: система скачает и поставит Python 3, инструмент для виртуальных окружений и git. Флаг -y заранее отвечает «да» на вопрос «точно ставим?».

Шаг 2. Забираем код бота на сервер

Код «Цыплёнка» лежит у тебя на GitHub (а если ещё нет — самое время залить, только не забудь про .gitignore, чтобы туда не попал секретный токен). Клонируем репозиторий:

git clone https://github.com/ozhik/chicken-bot.git
cd chicken-bot

Результат: на сервере появится папка chicken-bot с твоим bot.py и файлом requirements.txt, в котором перечислены нужные библиотеки.

Шаг 3. Виртуальное окружение и зависимости

Виртуальное окружение — это отдельная коробка с библиотеками только для одного проекта, чтобы версии не путались между разными ботами. Создаём коробку, входим в неё и ставим всё из списка:

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Результат: в начале строки появится (venv) — значит, ты внутри окружения, и сюда установятся aiogram, aiohttp и остальное из requirements.txt.

Шаг 4. Кладём токен в переменную окружения

Помнишь урок про переменные окружения и секреты? На сервере действует то же правило: токен в коде писать нельзя. Мы кладём его рядом, в файл .env, который никогда не попадает в git. Создадим такой файл прямо на сервере любым текстовым редактором, например nano:

nano .env

Внутри пишем одну строку и сохраняем (в nano — это Ctrl+O, Enter, потом Ctrl+X):

BOT_TOKEN=123456789:AaBbCcDdEeFf-твой-секретный-токен

Результат: токен хранится отдельно от кода, а бот достаёт его строкой os.getenv("BOT_TOKEN") — ровно так, как ты уже делал в прошлом уроке.

Теперь можно проверить, что бот в принципе запускается:

python3 bot.py

Результат: в консоли побегут логи запуска, а в Telegram «Цыплёнок» снова начнёт отвечать на /start. Но если ты сейчас закроешь SSH-сессию, бот опять умрёт — мы запустили его «вручную». Это и решает следующий шаг.

systemd — невидимый дворецкий, который держит бота включённым

Запускать бота вручную и держать окно открытым — это как стоять и держать дверь, чтобы она не закрылась. Неудобно и ненадёжно. В Linux есть systemd — это дворецкий операционной системы: он умеет запускать программы при старте сервера, перезапускать их, если они упали, и писать логи. Мы просто оставим ему записку («сервис»), где опишем, какую программу и как запускать.

Создаём файл сервиса:

nano /etc/systemd/system/chicken-bot.service

И пишем внутрь такую записку для дворецкого:

[Unit]
Description=Chicken Helper Telegram Bot
After=network.target

[Service]
WorkingDirectory=/root/chicken-bot
EnvironmentFile=/root/chicken-bot/.env
ExecStart=/root/chicken-bot/venv/bin/python3 bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Разберём построчно, чтобы это не было магией:

  • After=network.target — запускать бота только после того, как на сервере появился интернет (иначе боту не с кем разговаривать).
  • WorkingDirectory — папка, из которой запускать; как будто дворецкий сначала зашёл в неё командой cd.
  • EnvironmentFile — тот самый .env с токеном; systemd сам прочитает его и подставит переменные.
  • ExecStart — главная строка: каким именно Python (из нашей venv!) запускать bot.py.
  • Restart=always и RestartSec=5 — если бот вдруг упадёт, подними его снова через 5 секунд. Это и есть та самая надёжность.

Теперь говорим systemd перечитать записки, включить наш сервис в автозапуск и стартовать его:

systemctl daemon-reload
systemctl enable chicken-bot
systemctl start chicken-bot

Результат: бот запущен в фоне и будет подниматься сам после любой перезагрузки сервера. Проверить, жив ли он, можно командой:

systemctl status chicken-bot

Результат: увидишь зелёное active (running) — значит, «Цыплёнок» на дежурстве. А посмотреть его логи (что он печатал) можно так:

journalctl -u chicken-bot -f

Результат: побегут свежие строки логов в реальном времени; выйти из просмотра — Ctrl+C (бот при этом не остановится).

Docker — переезд вместе с домом, а не только с вещами

systemd — отличный вариант. Но есть проблема: твой бот работает не сам по себе, ему нужны Python нужной версии, набор библиотек, переменные. На новом сервере всё это придётся ставить заново и молиться, чтобы версии совпали. Знакомая боль: «у меня на ноутбуке работало, а на сервере нет».

Docker решает это радикально. Представь, что вместо того чтобы перевозить вещи в новую квартиру и заново всё собирать, ты перевозишь целую комнату вместе со стенами, мебелью и розетками. Docker упаковывает бота вместе с его Python, библиотеками и настройками в образ (image) — этакую коробку-комнату. Из образа запускается контейнер — работающая копия, изолированная от всего остального. Один и тот же образ одинаково запустится на твоём ноутбуке, на VPS и на сервере друга.

Пишем Dockerfile

Dockerfile — это рецепт сборки коробки. Ты описываешь по шагам, что положить внутрь, а Docker по рецепту собирает образ. Создаём файл с именно таким именем (без расширения) в папке бота:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python3", "bot.py"]

Читаем рецепт сверху вниз:

  • FROM python:3.12-slim — берём готовую заготовку: миниатюрный Linux, в котором уже стоит Python 3.12. slim значит «лёгкая версия без лишнего».
  • WORKDIR /app — внутри коробки делаем папку /app рабочей.
  • COPY requirements.txt . — копируем сначала только список зависимостей. Хитрость: пока он не меняется, Docker не будет переустанавливать библиотеки заново при каждой сборке.
  • RUN pip install ... — ставим все библиотеки из списка. Флаг --no-cache-dir не захламляет образ.
  • COPY . . — копируем остальной код бота внутрь.
  • CMD [...] — команда, которая выполнится при запуске контейнера: запустить bot.py.

Сразу создай рядом файл .dockerignore, чтобы лишнее не попало в образ (особенно секреты):

venv
.env
__pycache__
*.pyc
.git

Результат: при сборке Docker проигнорирует виртуальное окружение, секретный .env и мусор — образ останется чистым и лёгким.

Собираем образ и запускаем контейнер

На сервере должен быть установлен Docker (на свежем VPS обычно ставится командой apt install -y docker.io). Дальше — два шага. Сначала собираем образ по рецепту, давая ему имя-метку:

docker build -t chicken-bot .

Результат: Docker пройдёт по строкам Dockerfile, скачает заготовку с Python, поставит библиотеки и соберёт образ с меткой chicken-bot. Точка в конце — это «бери рецепт из текущей папки».

Теперь запускаем контейнер из образа. Токен передаём через файл .env — он не зашит в образ, а подаётся снаружи при запуске:

docker run -d --name chicken --restart=always --env-file .env chicken-bot

Результат: «Цыплёнок» запустится в контейнере в фоне и снова начнёт отвечать в Telegram. Разберём флаги:

  • -d — запустить в фоне (detached), не занимая консоль.
  • --name chicken — дать контейнеру понятное имя, чтобы потом обращаться к нему по нему.
  • --restart=always — поднимать контейнер заново, если он упал или сервер перезагрузился (аналог нашего systemd-правила).
  • --env-file .env — подставить переменные (токен!) из файла.

Посмотреть, что бот жив, и почитать его логи:

docker ps
docker logs -f chicken

Результат: docker ps покажет список работающих контейнеров (среди них наш chicken), а docker logs -f выведет логи бота в реальном времени.

Маленький разбор: как код достаёт токен

Чтобы и systemd, и Docker могли просто «подставить» токен снаружи, в коде бота он читается из окружения. Вот мини-логика этого чтения на чистом Python — её можно запустить прямо в браузере:

import os

# имитируем, что окружение задало переменную (на сервере это делает .env)
os.environ["BOT_TOKEN"] = "123456:SECRET"

token = os.getenv("BOT_TOKEN")
if not token:
    print("Токен не найден! Проверь .env")
else:
    # никогда не печатаем токен целиком в реальном боте
    print("Токен получен, длина:", len(token))
    print("Начинается на:", token[:6])

Вывод:

Токен получен, длина: 13
Начинается на: 123456

Видишь — код не знает и не должен знать, откуда именно пришёл токен: из .env, из systemd или из --env-file. Он просто берёт его из окружения. Поэтому одну и ту же программу можно деплоить любым из трёх способов.

Частые ошибки и подводные камни

На этих граблях спотыкаются почти все, кто деплоит бота первый раз. Пробежимся, чтобы ты их обошёл.

  • Токен уехал в git. Самая болезненная ошибка: забыл добавить .env в .gitignore, запушил — и твой токен теперь видит весь интернет. Telegram такие токены отзывает, а чужой бот может начать рассылать спам от твоего имени. Всегда проверяй .gitignore до первого git push, а если уже слил токен — немедленно перевыпусти его у BotFather командой /revoke.
  • Запустил два экземпляра сразу. Если бот крутится и в systemd, и в Docker (или просто запущен дважды), Telegram начнёт ругаться ошибкой вроде Conflict: terminated by other getUpdates request. Один токен — один работающий polling-бот. Останови лишнее: systemctl stop chicken-bot или docker stop chicken.
  • В сервисе указан системный python вместо venv. Если в ExecStart написать просто python3 вместо пути к venv/bin/python3, бот не найдёт aiogram и упадёт с ModuleNotFoundError. Дворецкий запускает ровно то, что ты написал, — указывай полный путь до Python из виртуального окружения.
  • Забыл про перезапуск после правок. Поменял код, сделал git pull — но бот в памяти всё ещё старый. Для systemd нужен systemctl restart chicken-bot, для Docker — пересобрать образ (docker build) и пересоздать контейнер. Сам по себе код на сервере не подхватывается.
  • База SQLite пропала вместе с контейнером. Если «Цыплёнок» хранит пользователей в SQLite, а ты удалил и пересоздал контейнер, данные внутри него исчезнут — контейнер по своей природе одноразовый. Чтобы база жила между перезапусками, её папку нужно подключать снаружи через том: -v /root/chicken-data:/app/data. Тогда файл базы лежит на сервере, а не внутри коробки.

Мини-практика: подними «Цыплёнка» сам

Теперь твоя очередь. Возьми своего бота и доведи его до жизни на сервере одним из двух путей — выбери, что больше нравится.

  1. Заведи самый дешёвый VPS у любого хостера, подключись по SSH и поставь Python с git (Шаги 1–3 выше).
  2. Залей бота на GitHub, обязательно добавив .env и venv в .gitignore. Проверь глазами, что токена в репозитории нет.
  3. Путь А (systemd): создай файл chicken-bot.service, включи его через systemctl enable и убедись, что systemctl status показывает active (running).
  4. Путь Б (Docker): напиши Dockerfile и .dockerignore, собери образ docker build -t chicken-bot . и запусти контейнер с --restart=always.
  5. Финальная проверка: перезагрузи сервер командой reboot, подожди минуту, переподключись и напиши боту /start. Если «Цыплёнок» ответил без твоего вмешательства — ты сделал настоящий деплой, поздравляю!

Со звёздочкой: добавь подключение тома для SQLite-базы, чтобы список пользователей переживал пересоздание контейнера.

Итоги

Сегодня «Цыплёнок-помощник» переехал с твоего ноутбука в настоящий дом и теперь работает круглосуточно. Ты научился: подключаться к VPS по SSH и ставить туда Python с зависимостями; запускать бота как сервис через systemd, который сам поднимает его после падений и перезагрузок; писать Dockerfile и упаковывать бота в образ; запускать его в контейнере одной командой docker run. А ещё — главное правило безопасности: токен живёт в переменной окружения и никогда не уезжает в git.

Теперь у твоего бота есть постоянная крыша над головой. В следующем уроке мы сделаем так, чтобы обновлять бота на сервере было не больно: настроим автоматический деплой, чтобы после git push свежая версия «Цыплёнка» сама приезжала на сервер и перезапускалась. Больше никаких ручных git pull и restart по ночам.

Проверьте себя
1. Зачем боту вообще нужен VPS, а не запуск на твоём ноутбуке?
AVPS работает 24/7 и не выключается, когда ты закрываешь ноутбук
BНа ноутбуке нельзя установить Python
CTelegram запрещает запускать ботов на личных компьютерах
DVPS делает код бота быстрее в десятки раз
2. Что делает строка Restart=always в файле сервиса systemd?
AПерезапускает сервер каждый час
BАвтоматически поднимает бота снова, если он упал или сервер перезагрузился
CУдаляет старые логи бота
DЗапрещает боту перезапускаться вручную
3. Чем образ (image) отличается от контейнера в Docker?
AЭто одно и то же, просто разные слова
BОбраз — рецепт, контейнер — список библиотек
CОбраз — упакованная коробка с ботом и его окружением, контейнер — её работающая копия
DКонтейнер собирается из Dockerfile, а образ запускается командой run
4. Почему токен бота не пишут прямо в Dockerfile или в коде, а передают через --env-file .env?
AТак быстрее собирается образ
BЧтобы секрет не попал в образ и в git, откуда его смог бы украсть кто угодно
CDocker не умеет читать строки длиннее 20 символов
DЭто требование aiogram 3.x
5. Бот выдаёт ошибку Conflict: terminated by other getUpdates request. Что вероятнее всего случилось?
AСервер потерял интернет
BИстёк срок действия токена
CС одним токеном одновременно запущено два экземпляра бота
DНе хватило места на диске
6. Что произойдёт с базой SQLite, если она лежит внутри контейнера, а ты пересоздашь контейнер без подключённого тома?
AДанные сохранятся автоматически в облаке Docker
BДанные внутри контейнера исчезнут вместе со старым контейнером
CБаза переедет в systemd
DНичего, контейнеры хранят данные вечно