Как задеплоить сайт, написанный с Claude Code, на свой VPS

Обсудить
Как задеплоить сайт, написанный с Claude Code, на свой VPS
Реклама. АО «ТаймВэб». erid: 2W5zFHJM7Xo

Вайбкодинг закрывает разработку – 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

echo -e "Все про серверы, сети, хостинг и еще раз серверы" >/dev/pts/0

Комментарии

С помощью соцсетей
У меня нет аккаунта Зарегистрироваться
С помощью соцсетей
У меня уже есть аккаунт Войти
Инструкции по восстановлению пароля высланы на Ваш адрес электронной почты.
Пожалуйста, укажите email вашего аккаунта
Ваш баланс 10 ТК
1 ТК = 1 ₽
О том, как заработать и потратить Таймкарму, читайте в этой статье
Чтобы потратить Таймкарму, зарегистрируйтесь на нашем сайте