Production-деплой на VPS: практичный путь для вашего первого проекта
Первый вывод своего проекта на сервер — самый пугающий этап для многих разработчиков. Писать код вы умеете, но слова «deploy», «nginx», «systemd» кажутся другим миром. Я несколько лет администрирую серверы школьных систем и говорю из этого опыта: production-деплой — не магия, а понятная последовательность шагов, которую достаточно выучить один раз. Ниже показываю её пошагово, без лишней сложности.
Почему VPS, а не PaaS?
PaaS вроде Vercel, Railway или Render очень удобны: сделал git push — сайт уже живой. Но в местных реалиях три причины работают в пользу VPS:
- Цена. На VPS за 4–8 долларов спокойно живут несколько проектов, база и cron-задачи. В PaaS каждый сервис — отдельные деньги, база — ещё отдельно; с ростом числа проектов разница становится ощутимой.
- Реалии оплаты. Работать с сервисами, которые ежемесячно списывают с международной карты, в Узбекистане удобно не всем. Местные провайдеры принимают оплату в сумах, а при необходимости дадут и договор.
- Полный контроль. Что ставить, какую версию, как настраивать — решаете вы. В работе со школьными системами для меня это решающий фактор: должно быть точно известно, где лежат данные и кто имеет к ним доступ.
Честно о минусах: безопасность, обновления, мониторинг — всё на вас. Если сервер упадёт ночью, поднимать его будете вы. Если ваш проект — один лендинг, а время дорого, PaaS может оказаться действительно лучшим выбором. Но для реального проекта с бэкендом, базой и постоянным трафиком VPS стоит освоить.
Минимальная база безопасности
Как только новый VPS появляется в интернете, боты начинают перебирать пароли — это не гипотеза, откройте auth-логи и убедитесь сами. Поэтому в первые 15 минут делаем четыре вещи:
# 1. Загрузить SSH-ключ на сервер (на локальной машине)
ssh-copy-id deploy@server-ip
# 2. Отключить root-логин и вход по паролю
# в /etc/ssh/sshd_config:
# PermitRootLogin no
# PasswordAuthentication no
sudo systemctl restart sshd
# 3. Firewall: открыты только нужные порты
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
# 4. fail2ban — автоматически блокирует повторные неудачные попытки
sudo apt install fail2ban -yЭто не полный security-аудит, но именно эта база останавливает большую часть реальных атак.
Node.js-приложение как systemd-сервис
Многие начинают с pm2, и это неплохо. Но на сервере уже есть профессиональный process manager — systemd. Он идёт вместе с ОС, не требует отдельного демона, складывает логи в journald и сам поднимает сервис после перезагрузки сервера. pm2 же пригодится, когда нужен cluster mode или его собственный интерфейс мониторинга. Для одного приложения systemd проще и достаточен.
Файл /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 dist/server.js
Restart=always
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=/home/deploy/myapp/.env
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now myapp
journalctl -u myapp -f # смотреть логи в реальном времениБлагодаря Restart=always после падения приложение само поднимется через 5 секунд — ночных звонков станет меньше.
nginx: reverse proxy, gzip и кэш статики
Приложение работает на порту 3000, а миру через 80/443 виден nginx. /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name example.uz;
gzip on;
gzip_types text/css application/javascript application/json;
# Статику отдаёт сам nginx — приложение не нагружается
location /static/ {
alias /home/deploy/myapp/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Проверка и включение: sudo nginx -t && sudo systemctl reload nginx.
HTTPS: certbot за 2 минуты
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.uzcertbot сам обновит конфиг nginx и автоматически продлит сертификат до истечения срока. Времена «SSL — это дорого и мучительно» давно прошли: сегодня HTTPS бесплатен и является обязательным стандартом.
Обновление без простоя — самый простой способ
Сложная оркестрация не нужна. Простой deploy-скрипт:
#!/usr/bin/env bash
set -e
cd /home/deploy/myapp
git pull origin main
npm ci --omit=dev
npm run build
sudo systemctl restart myapp
# Health check: ждём, пока приложение ответит
for i in {1..10}; do
sleep 2
curl -fs http://127.0.0.1:3000/health && echo "Deploy OK" && exit 0
done
echo "Deploy FAILED" && exit 1При таком способе будет пауза в 2–3 секунды — для большинства проектов пользователь её даже не заметит. Если нужен настоящий zero-downtime, поднимаете новую версию рядом на порту 3001, после прохождения health check меняете порт в proxy_pass и делаете reload — nginx при reload не рвёт активные соединения. Оба способа умещаются в один bash-скрипт, Kubernetes на этом этапе не нужен.
Нужен ли Docker?
Прагматичный ответ: в начале — нет. Для одного Node-приложения и одного Postgres Docker — дополнительный слой: сборка образов, volume, network — нагрузка на обучение растёт, а пользы пока не видно. Docker оправдывает себя, когда:
- В проекте несовместимые runtime (разные версии Node или Python)
- Одно и то же окружение нужно переносить на несколько серверов
- Команда выросла, и проблема «а у меня работает» начинает дорого стоить
В школьных системах я использую Docker именно по второй причине: одну систему нужно разворачивать в одинаковом виде на десятках серверов. Для одного проекта на одном VPS полностью хватает systemd + nginx.
Backup: простейший cron + pg_dump
Production без бэкапов — бомба замедленного действия. Простейшая рабочая схема:
# /home/deploy/backup.sh
#!/usr/bin/env bash
set -e
DIR=/home/deploy/backups
DATE=$(date +%F)
pg_dump -U myapp mydb | gzip > "$DIR/db-$DATE.sql.gz"
# Удаляем копии старше 14 дней
find "$DIR" -name "db-*.sql.gz" -mtime +14 -delete# crontab -e — каждый день в 03:00
0 3 * * * /home/deploy/backup.sh >> /home/deploy/backup.log 2>&1Важное правило: если бэкап лежит только на самом сервере — это не бэкап. Хотя бы раз в неделю копируйте его в другое место: на другой сервер, в object storage или даже на личный компьютер. И раз-два в год пробуйте restore: от бэкапа, который нельзя восстановить, никакой пользы нет.
Итог
Деплой на VPS — ремесло, которое учат один раз: SSH-ключ, ufw, systemd unit, конфиг nginx, certbot, простой deploy-скрипт и cron-бэкап. С этими семью шагами ваш проект работает стабильно и на профессиональном уровне. Когда проект вырастет, добавите сверху CI/CD, мониторинг, при необходимости Docker — но фундамент останется тем же.