Появилась идея для серии постов про наши вайб‑кодерские проблемы. Сегодня я расскажу про шишки, которые набила в самой сложной для меня части — в деплое.
Мой любимый пет‑проект был про астрологию: человек присылал в один популярный мессенджер данные рождения, сервис считал натальную карту, а ИИ отвечал на вопросы по ней. За полгода проект не принёс ни цента, и в один прекрасный день в коридоре затмений я его закрыла. Зато в процессе мучений с ним я прошлась по некоторому количеству граблей и хочу поделиться ими с вами.
Итак. У тебя есть гениальная идея гениального сервиса. Ты обсудил её с ИИ, ИИ выдал код и сулит деньги рекой. Осталась мелочь: этот код пока живёт и, вероятно, работает только на твоём ноутбуке. А надо, чтобы в интернете.
Куда вообще выкладывать
Вариантов я вижу три, и два из них отсеиваются сами.
Локально, со своего компьютера. Надо пробросить порты, настроить домен, следить, чтобы машина не уснула, разобраться с безопасностью, не выключать ноут никогда. Я так не могу. Я котик.
На арендованном пустом сервере. Дали тебе голую машину — сам ставь обновления, файрвол, веб‑сервер, сертификаты, мониторинг, сам всё патчь до конца времён. Так я тоже не могу. У меня лапки.
Значит — специализированный хостинг, который часть этого всего берёт на себя. Дальше вопрос: какой.
По каким критериям я выбирала хостинг
У меня было три.
1. Работа с микросервисами
У меня два микросервиса: один считал натальные карты, второй общался с человеком через ИИ. Ты, может, скажешь, что это оверкилл, но у меня была серьёзная важная причина, о которой я может быть расскажу отдельным постом (спойлер: ИИ может незаметно притащить тебе зависимость с лицензией, которая обязывает открыть твой закрытый код — и ой).
Независимо от причин, мне надо было раскатать два микросервиса, и чтобы один смотрел в интернет, второй слушал только первый. Не у всех хостингов это достаточно просто настраивается.
Кстати, важная заметка для твоего кошелька: каждый микросервис — это отдельный счёт. Два микросервиса внутри одного моего проекта = я плачу за два. Держи это в голове, когда ИИ бодро предлагает тебе «давай вынесем это в отдельный сервис».
2. Работа с базами данных
Если у тебя БД нет — пропускай пункт. У меня она была: надо хранить натальные карты, а ещё подписки и даты платежей (как будто они были). У меня база хостилась отдельно и бесплатно.
Вот это «бесплатно» я пока аккуратно пропущу, потому что бесплатная БД может стать проблемой, и это заслуживает отдельного поста. Пока запомни одно: когда выбираешь хостинг, отдельно смотри, как он хостит базы. Многие берут за это деньги, а бесплатные тиры могут неприятно удивить.
3. Способ настройки: код против галочек
Есть хостинги, которые позиционируют себя как «для совсем новичков»: все настройки через красивый интерфейс, кликай галочки. Звучит классно, но я бы не стала. Вот почему:
ты привязан к этому хостингу навсегда — твоя конфигурация живёт у них в интерфейсе, а не у тебя;
за галочками надо следить и расставлять их руками, а рук у тебя две и память дырявая;
и главное — ты не можешь попросить ИИ проверить за тебя галочки. А вот код — можешь.
Отсюда внезапный вывод: Infrastructure as Code — это идеальный вайб‑кодерский вариант. Не advanced‑тема для бородатых девопсов, а именно спасение для новичка. Твоя инфраструктура описана текстовым файлом, файл лежит рядом с кодом, и его можно скормить ИИ со словами «посмотри, я нигде не накосячил?» Галочки так не проверишь, ты их сначала все найди в этом красивом модном интерфейсе.
Почему мне зашёл Fly.io
Я попробовала несколько хостингов. Те, что не подошли, описывать не буду — вдруг их юристы читают по‑русски. А вот понравился Fly.io, и вот за что:
тот самый Infrastructure as Code из коробки;
деплой одной командой прямо из папки —
fly deploy, без обязательного гита и пушей, просто из директории с кодом;настройки, кто с кем говорит по сети — какой сервис принимает запросы из интернета, а какой отвечает только другому сервису.
Здесь могла бы быть их реклама, но это не она, поэтому расскажу и про сложности. Мне сначала казалось, что Fly — это магия: берёт мой код и сам придумывает, как его запустить. Ничего он не придумывал. Он аккуратно исполнял четыре файла, которые я до этого написала сама (шучу, конечно, какое сама, ИИ мне их написал):
Dockerfile(две штуки, по одной на сервис) — были у меня изначально, потому что запустить библиотеку с расчётами астрологии без него мне не удалось. Но вообще, искренне от души рекомендую всю локальную разработку вести через контейнеры — спасает очень много нервов. Но, увы и ах, с докером всё‑таки наконец в 2026м придётся разобраться.fly.toml(тоже две штуки) — описание, как сервис живёт на платформе: имя, регион, размер машины, какие порты торчат наружу, health‑чеки. Я подробно про них напишу ниже.
Команда fly deploy читает fly.toml из папки, собирает образ по Dockerfile, заливает его и раскатывает машину по описанным параметрам. И никакой магии.:(
Ниже. Про файлы fly.toml
Напомню расклад: у меня два приложения. dearstarsbot — болталка, он смотрит наружу. sweservice — считалка натальных карт, она должна слушать только бота и больше никого. Болталка ходит к считалке за расчётом. Вот и вся топология.
Болталка
app = 'dearstarsbot' primary_region = 'fra' [build] build-target = "bot" [env] METRICS_PORT = "8081" CHART_API_URL = "http://sweservice.flycast/chart" CITY = "London" OPENAI_MODEL = "gpt-4o" SUPPORT_CHAT_ID = "12345666" [http_service] internal_port = 8081 force_https = true auto_stop_machines = 'off' auto_start_machines = true min_machines_running = 1 max_machines_running = 1 processes = ['app'] [[http_service.checks]] interval = "10s" timeout = "2s" grace_period = "5s" method = "GET" path = "/health" [[vm]] memory = '1gb' cpu_kind = 'shared' cpus = 1
Идём построчно по тому, что не очевидно:
build-target = "bot"— нужно если у тебя в Dockerfile несколько стадий (FROM ... AS bot), тогда Fly собирает не весь Dockerfile целиком, а конкретную его стадию с именемbot. Если стадий нет, строчка не нужна.[env]— это открытая конфигурация: порт для метрик, адрес второго сервиса, город по умолчанию, модель OpenAI и мой личный chat id, чтобы получать оповещения. Заметь: ключей здесь нет.CHART_API_URL = "http://sweservice.flycast/chart"— первая ниточка между сервисами. По этому адресу болталка ходит за расчётом карты. Разбираем адрес:sweservice— это имя второго приложения,.flycast— его приватный адрес внутри моей сети на Fly, аhttp://(неhttps!) — потому что flycast принципиально ходит только по http. Запомните этоhttp, оно ещё появится.internal_port = 8081— порт, который болталка слушает внутри контейнера. Функционально у болталки только исходящий трафик (запросы в апи мессенджера), но у меня ещё была статистика, которую можно было взять как раз на этом порту.force_https = true— публичный сервис, гоним всех на https. Логично.auto_stop_machines = 'off'+min = 1+max = 1— болталка живёт ровно в одном экземпляре и никогда не засыпает. Почему так — расскажу в секции про грабли.[[http_service.checks]]— Fly раз в 10 секунд дёргает/healthи решает, живой ли сервис. Про это тоже будет в граблях.[[vm]]— параметры машины, на которой Fly запустит приложение.cpu_kind = 'shared'— процессор «в общем пользовании», я расскажу о нём чуть ниже, но не в граблях.
Считалка:
app = 'sweservice' primary_region = 'fra' [build] build-target = "final" [env] METRICS_PORT = "8080" CGO_ENABLED = 1 CITY = "London" [http_service] internal_port = 8080 force_https = false auto_stop_machines = 'stop' auto_start_machines = true min_machines_running = 1 max_machines_running = 1 processes = ['app'] [[vm]] memory = '1gb' cpu_kind = 'shared' cpus = 1
Здесь смотрим в основном на отличия:
CGO_ENABLED = 1— довольно экзотическая строчка, нужна, только если у тебя, как у меня, Go поверх С: внутри этого сервиса живёт C‑шная библиотека для расчётов. Если у тебя не Go поверх C — забудь, что видел эту строку.internal_port = 8080— вторая ниточка между сервисами. ПомнишьCHART_API_URL = ".../chart"у болталки? Так вот/chartживёт именно на этом порту. Порт, который слушает считалка, — это ровно тот порт, на который стучится болталка.force_https = false— и это не забывчивость, а требование. flycast работает только по http, и если поставить сюдаtrue, приватная связь между сервисами просто сломается. То самоеhttp://из адреса болталки, только с другой стороны.auto_stop_machines = 'stop'— вроде бы «пусть засыпает, когда никто не спрашивает». Но рядом стоитmin_machines_running = 1, и единица не даёт машине уснуть. То есть'stop'здесь тихо не работает. Упс.
И ещё один упс: посмотри на оба файла ещё раз и найди строчку, которая делает sweservice приватным.
Её там нет.
Оба сервиса объявляют [http_service]. Оба, если верить только fly.toml, — публичные. Приватность в конфиге не описана вообще.
Она берётся из другого места: у sweservice просто нет публичного IP‑адреса — только приватный flycast. К сожалению, это никак не сделать через fly.toml, только через красивый интерфейс или терминал:
fly ips allocate-v6 --private # выдать приватный flycast-адрес fly ips list # проверить, что публичных адресов нет fly ips release <адрес> # снять публичный, если Fly уже его навесил
Коварство ещё и в том, что при первом деплое сервиса Fly сам вешает на него публичный IP. То есть по умолчанию ты открыт всему интернету, и приватность — это активное действие: пойти и снять адрес. Забыл — и твоя внутренняя считалка торчит наружу.
Почему бы не управлять этим всё‑таки из самого логичного места — файлов с настройками? Очень хороший вопрос, который создателям Fly задают примерно все. Ну, возможно у них есть эта задача в бэклоге.
Про shared CPU
Интересная настройка машины из моего fly.toml (или из интерфейса), давай объясню.
Fly не отдаёт тебе ядро процессора целиком, а нарезает его между кучей чужих машин: ты арендуешь не ядро, а кусочки его времени. Сколько тебе кусочков? В среднем — около 6% одного ядра: как будто твой процессор работает секунды 3–4 из каждой минуты. Звучит нищенски, но есть фокус: пока ты простаиваешь, копится право на рывок, и в нужный момент ты хватаешь целое ядро на 100% на несколько минут. То есть shared — это не «всегда медленно», а «быстро короткими рывками, медленно если долбить процессор без остановки».
Кому это не подходит. Подними я на этой машине какой‑нибудь сервер Counter‑Strike — он грузит CPU непрерывно, каждый тик — право на рывок выгорело бы за минуты, и дальше игра лагала бы в те самые 6%. Всё, что использует процессор ровно и без пауз (игровой сервер, перекодировка видео, майнинг), на shared не жилец.
А для моих задач это идеально. Расчёт натальной карты — секунды три CPU, но операция разовая: пользователь посчитал один раз, результат лёг в базу данных, и дальше все вопросы к базе, а не к считалке. Даже на миллионах юзеров (ах если бы) я спокойна — миллиона новых за час у меня не будет, а старые карты уже посчитаны. Болталка процессор тоже почти не трогает: самое долгое там — дождаться ответа от ИИ, а это ожидание я делаю асинхронно, CPU в это время свободен. Обе мои нагрузки — это рывок раз в кои‑то веки, а не постоянная нагрузка. Идеальный клиент для shared.
О чём ещё можно задуматься, ставя эту галочку: у меня же данные живых людей — не утекут ли они соседу по ядру? Не утекут. Ты делишь процессорное время, а не память: каждая машина — изолированная виртуалка, чужой код не лезет ни в твою оперативку, ни в твою базу.
Короче, для меня это был прям хороший способ сэкономить — и я им воспользовалась.
Обещанные грабли, которые я обошла (но на одну наступила)
Грабли 1: Ключи или самый дорогой прокол на ровном месте. Деплой — это не только «куда положить код». Это ещё «как положить ключи, чтобы они не утекли». У меня их было три: ключ к ИИ‑провайдеру, токен бота мессенджера, доступы к базе.
Куда их девать? Не в код и не в fly.toml. Классика вайб‑кодинга: ИИ захардкодил ключ, ты это закоммитил в публичный репозиторий, а через пару дней у тебя ноль или минус на счету, потому что кто‑то нашёл твой ключ и погонял на нём свои задачи.
Кто‑то может скажет мне, но репозиторий же можно сделать приватным! А я отвечу, какой‑нибудь айклауд, например, тоже приватный, и сколько раз его ломали. Ключи должны быть зашифрованы, а не просто лежать в закрытом месте.
У Fly для этого есть красивый интерфейс команда в терминале fly secrets set
fly secrets set OPENAI_API_KEY=sk-... BOT_TOKEN=...
Ключи прокидываются в машину как переменные окружения при запуске и не попадают в собранный образ.
Это надо сделать один раз и забыть, но Fly помнит — в зашифрованном виде. Посмотреть, какие ключи уже заданы, можно, а сами значения — нет: fly secrets list покажет только их хэши.
NAME DIGEST DATE OPENAI_API_KEY b9e37b7b239ee4… 2 days ago BOT_TOKEN cdbe3268a82bfe… 2 days ago
Грабли 2: Ну там же был health check! И Fly с ним как‑то работал, но не так, как я думала. Помнишь в моём fly.toml — секцию checks, чтобы раз в 10 секунд стучаться в /health? Это Fly проверяет: «ты там живой?». Приложение отвечает «ага» — значит живо, всё хорошо.
Я думала, что если «не ага», то Fly его перезапустит, но нет. Он просто перестаёт слать на него трафик. Опция перезапуска есть, но это надо чтобы приложение совсем упало, чтобы прям вот вышло, с exit code каким‑нибудь страшным. А не упало — так и ладно.
И вот короче пью я чай, а кто‑то ломает мою болталку, причём так хитро, что она зависает, но не падает. /health говорит «не ага», Fly молча убирает трафик. А у меня, знаешь ли, polling, там и так входящего трафика нет, мне эта мера никак не помогает. Помог друг, который решил посоветоваться с ботом, не сменить ли ему работу, а бот ему не ответил, и друг написал мне.
Окей, грабли прилетели, что я с ними делала. Во‑первых, стала паниковать на всё, на что могла — пусть сервис лучше честно упадёт и воскреснет по полиси, чем зависнет молча. Во‑вторых, запасной план на случай непредвиденного: раз в день я сама писала боту и смотрела, отвечает ли он. Я у мамы инженер.
Грабли 3: Держи по две машины на каждый сервис. Хостинг настойчиво предлагает для каждого сервиса поднимать две копии — ради безотказности. В теории, это спасло бы меня от грабель 2: зависшее приложение висело бы дальше, его копия продолжала бы отвечать, и так до скончания времён. Но это в теории и для большинства сервисов, но не для меня. Моя болталка, которая опрашивает апи мессенджера, должна существовать в единственном экземпляре, иначе две копии начинают конфликтовать и сыпать ошибками. Вот сейчас я это пишу и думаю, что проблему можно было бы обойти, если бы я вместо polling делала через webhook, но это вот я сейчас думаю, а тогда я так не думала.
И кстати, за две машины тоже пришлось бы платить в два раза больше.
Грабли 4: Экономия и Scale‑to‑zero. Хостинги предлагают дешёвый режим: сервис засыпает в простое и включается, когда приходит запрос. Дешевле — да, потому что чаще всего оплата сейчас «по использованию». Но первый запрос после сна ждёт пробуждения несколько секунд. Я это даже не пробовала: мне сразу было очевидно, что для чат‑бота пауза в несколько секунд = потерянный человек. Хотя считалка была бы отличным кандидатом на такую экономию, но тогда у меня ещё были деньги и надежда заработать ещё больше, так что я сразу взяла чуть более дорогой вечно работающий тир.
Кстати, а сколько всё это стоило?
Около 13 долларов в месяц — за две всегда включённые машины моего размера (гиг памяти, shared CPU). Самый короткий раздел.
К чему это всё
ИИ хорош. Он напишет код и расставит по статье длинные тире. Он и выбор за тебя сделает: shared или нет, одна машина или две — вот только выберет, скорее всего, то, что хостинг советует по умолчанию или что считается нормой для всех, не задумавшись, подходит ли это именно тебе. Поэтому я и собрала здесь места, где на этапе деплоя стоит остановиться и подумать: каждое из них бьёт либо по проекту, либо по кошельку, а то и по обоим сразу.
Короче, думай головой, и да поможет нам ИИ.
В комментариях — спорьте. Расскажите, где я решила хорошо, а где — так себе. Истории вашего вайб‑деплоинга также приветствуются
andreiseliv
ИИ действительно может помочь, но вот эти моменты с деплоем, безопасностью и инфраструктурой как раз показывают, что без понимания основ всё равно легко попасть в неприятности.