Вайбкодинг закрывает разработку – Claude Code пишет рабочее приложение за вечер, оно крутится на localhost и выглядит готовым. Дальше начинается часть, про которую туториалы по вайбкодингу обычно молчат: перенос этого кода на настоящий сервер, где он будет работать без вашего ноутбука, переживёт перезагрузку и не свалится от первого же наплыва пользователей.
Это не то же самое, что развернуть на VPS ИИ-агента, который сам дёргает LLM API по подписке или ключу. Здесь – обычное веб-приложение: фронтенд, бэкенд, база данных. Оно не обращается к внешним AI-сервисам во время работы, поэтому вопросов с блокировками зарубежных API из России не возникает вообще – вся сложность в другом: окружение, процесс-менеджер, реверс-прокси, безопасность. Ниже – пошаговый маршрут с командами и комментариями к каждой из них.
Почему это не «просто скопировать файлы»
Разница между кодом на локальной машине и кодом в продакшене в том, что на локальной машине прощаются вещи, которые в проде становятся проблемой. Секретные ключи и пароли к базе, зашитые прямо в код или лежащие в файле рядом с ним, на ноутбуке никому не мешают. На сервере, доступном по IP всему интернету, это дыра в безопасности с первого дня. Процесс, который вы запускаете командой npm start в терминале и оставляете висеть, на локальной машине переживёт максимум перезагрузку ноутбука. На сервере он должен пережить обрыв SSH-сессии, перезапуск сервера после апдейта системы и случайное падение самого приложения – и подняться заново без вашего участия.
Дальше – по порядку: выбор сервера, подготовка окружения, перенос кода, процесс-менеджер, реверс-прокси и базовая безопасность.
Выбор сервера
Для типового веб-приложения на Node.js или Python с базой SQLite/Postgres достаточно самого младшего тарифа VPS – 1-2 vCPU и 1-2 ГБ RAM с запасом хватает, если это не сервис с тысячами одновременных пользователей. Регион сервера для обычного веб-приложения (без обращений к внешним AI API) можно выбирать по близости к аудитории – сервер в России подойдёт, если основные пользователи здесь же, задержка будет ниже.
Систему проще всего брать Ubuntu LTS – самая распространённая, у неё больше актуальных гайдов и пакетов из коробки.
Подготовка сервера
Первым делом – не работать под root на постоянной основе. Root даёт доступ вообще ко всему на сервере, и одна неверная команда (или один взломанный процесс) может снести систему целиком. Создаём отдельного пользователя и даём ему права через sudo:
# создать нового пользователя с домашней папкой
adduser deploy
# добавить его в группу sudo – сможет выполнять команды с повышением прав
usermod -aG sudo deploy
# переключиться на нового пользователя
su - deploy
Дальше – вход по SSH-ключу вместо пароля. Пароль можно перебрать, ключ на практике – нет:
# на локальной машине: сгенерировать пару ключей, если ещё нет
ssh-keygen -t ed25519 -C "deploy@myproject"
# скопировать публичный ключ на сервер (спросит пароль последний раз)
ssh-copy-id deploy@ВАШ_IP
После этого в /etc/ssh/sshd_config на сервере отключаем вход по паролю:
# запрещаем аутентификацию по паролю – только по ключу
PasswordAuthentication no
И перезапускаем службу: sudo systemctl restart sshd.
Последний шаг подготовки – файрвол. ufw (Uncomplicated Firewall) в Ubuntu уже стоит по умолчанию, нужно только включить и открыть нужные порты:
sudo ufw allow OpenSSH # не закрыть себе доступ по SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
Окружение: Node.js и переменные среды
Если приложение на Node.js – ставим через официальный репозиторий NodeSource: версия в стандартных пакетах Ubuntu обычно устаревшая.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
Код на сервер переносится через git clone (если репозиторий уже есть) или scp для разовой заливки. После переноса – установка зависимостей и, отдельно, секреты:
cd /home/deploy/myapp
npm install --production
Пароли от базы данных, токены и прочие секреты никогда не хардкодятся в код – они живут в файле .env, который не попадает в git (прописан в .gitignore):
# .env – создаётся вручную на сервере, не в репозитории
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SESSION_SECRET=случайная-длинная-строка
NODE_ENV=production
Хардкодить эти значения в коде – рабочий способ рано или поздно слить пароль в публичный репозиторий, если проект вдруг станет открытым или ключ скопируют в чат для отладки. Переменные окружения в отдельном файле снимают этот риск: файл физически не попадает в систему контроля версий.
Процесс-менеджер: почему не nohup
Частый первый инстинкт – запустить процесс через nohup node server.js & и закрыть терминал. Это работает ровно до первого падения приложения или перезагрузки сервера: nohup не перезапускает упавший процесс сам, и после ребута сервера ничего не поднимется, пока кто-то не зайдёт по SSH и не запустит команду руками.
Правильный инструмент – systemd, стандартный менеджер процессов в Ubuntu. Создаём юнит-файл:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js App
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
EnvironmentFile=/home/deploy/myapp/.env
[Install]
WantedBy=multi-user.target
Построчно: Restart=on-failure – перезапускает процесс, если он упал сам (не остановлен командой). RestartSec=5 – пауза перед перезапуском, чтобы не уйти в бесконечный цикл падений при системной проблеме. EnvironmentFile подтягивает переменные из .env без необходимости прописывать их в самом юнит-файле.
Дальше – включаем и запускаем:
sudo systemctl daemon-reload
sudo systemctl enable myapp # автозапуск при перезагрузке сервера
sudo systemctl start myapp
sudo systemctl status myapp # проверить, что запустилось
Теперь при падении процесса systemd перезапустит его сам, а при перезагрузке сервера – поднимет автоматически, без ручного вмешательства.
Реверс-прокси и HTTPS
Приложение на Node.js обычно слушает какой-то внутренний порт вроде 3000 – напрямую наружу его лучше не выставлять. Между интернетом и приложением ставится nginx: он принимает запросы на 80/443 порту и перенаправляет их на внутренний порт приложения, заодно отдавая статику быстрее, чем это сделал бы сам Node.js процесс.
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.example.com;
location / {
proxy_pass http://localhost:3000; # куда пересылать запросы
proxy_set_header Host $host; # сохраняем оригинальный домен
proxy_set_header X-Real-IP $remote_addr; # сохраняем реальный IP клиента
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # нужно для WebSocket, если используется
proxy_set_header Connection 'upgrade';
}
}
Активируем конфиг и получаем бесплатный SSL-сертификат через Let's Encrypt:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t # проверка синтаксиса перед перезапуском
sudo systemctl reload nginx
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.example.com # сам допишет HTTPS-конфиг и настроит автопродление
Кейс из практики: портал бюджетов за два дня и потерянный контекст
Денис Кулик – не программист, до этого проекта не разворачивал серверы вручную. В строительной компании прорабы вносили планы по объектам в разные Google-таблицы, финансисты вручную сводили их в общую – двойной ввод регулярно приводил к ошибкам в цифрах. Кулик поднял сервер в Нидерландах на Ubuntu и вместе с Claude Code за два дня (включая тестирование и доработки) собрал портал на собственном домене: бюджеты, факт, планы команд по объектам, прогнозы по единой методике расчёта – с учётом налоговых ставок и страховых взносов.
Ошибки на двойном вводе, которые раньше ловили постфактум, пропали – данные теперь вносятся один раз и сразу видны всем, кому нужно. Но был и сбой: после перезагрузки VS Code контекст разработки потерялся целиком, пришлось частично восстанавливать его заново. Причина – memory (файл с накопленным контекстом проекта, который Claude Code подгружает между сессиями) не была настроена с самого начала, поэтому терять было прямо нечему сохраняться.
Этот кейс разбирали в сообществе предпринимателей, которые внедряют ИИ в свои рабочие процессы. Обсуждали в том числе, почему memory стоит поднимать в первый же день работы над проектом.
Как смотреть логи, когда что-то пошло не так
Через systemd логи приложения смотрятся без отдельной настройки – journalctl собирает всё, что процесс пишет в stdout/stderr:
sudo journalctl -u myapp -f # -f держит поток открытым, как tail -f
sudo journalctl -u myapp -n 100 # последние 100 строк, без потока
Для nginx логи запросов и ошибок лежат отдельно и стоит проверять их тоже, особенно если приложение отвечает, а через прокси до пользователя ничего не доходит:
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
Разделение помогает быстро понять, на каком именно уровне проблема – приложение не отвечает вообще, или отвечает, но nginx неправильно его проксирует.
Базовый чек-лист безопасности
- Вход по SSH только по ключу, пароль отключён.
- Файрвол открывает только нужные порты (SSH, HTTP, HTTPS) – остальное закрыто по умолчанию.
- Секреты – в .env, не в коде и не в git.
- Приложение работает от отдельного пользователя с ограниченными правами, не от root.
- Автоматические обновления системы включены (sudo apt install unattended-upgrades), чтобы не пропустить критичный патч безопасности.
FAQ
Нужен ли Docker для такого деплоя? Не обязательно. Для одного простого приложения systemd + nginx проще в отладке новичку. Docker даёт пользу при нескольких сервисах на одном сервере или при частых переносах между окружениями – если задача разовая, лишний слой абстракции может только усложнить отладку.
Что делать, если после перезапуска сервера приложение не поднялось? Проверить sudo systemctl status myapp – команда покажет последние строки лога и причину падения. Частая причина – не настроен Restart=on-failure в юнит-файле или процесс ждёт переменную окружения, которой нет в EnvironmentFile.
Как не потерять контекст разработки, как в кейсе выше? Настроить memory-файл (или аналогичный механизм накопления контекста, если используется другой инструмент) сразу в начале проекта – не дожидаясь первой потери. Проще один раз потратить 10 минут на настройку, чем в середине работы восстанавливать по памяти, что уже было сделано.
Сколько стоит такой сервер? Для приложения с несколькими одновременными пользователями достаточно младшего тарифа VPS – конкретная цена зависит от провайдера и характеристик, но порядок для 1-2 vCPU/1-2 ГБ RAM обычно укладывается в несколько сотен рублей в месяц.
Что в итоге
Деплой вайбкодингового приложения технически не отличается от деплоя любого другого веб-сервиса – разница в том, что код писали не вы лично, поэтому стоит внимательнее относиться к тому, что в нём происходит на уровне переменных окружения и обработки ошибок. Пять шагов – сервер и пользователь без root, окружение и секреты в .env, systemd вместо nohup, nginx с HTTPS, базовый файрвол – закрывают процентов девяносто того, что может пойти не так в первую неделю продакшена.
Изображение на обложке: Magnific
Комментарии