У D.I.S.C. — приложения для командной оценки поведенческих стилей — есть свой сервер. Ведущий создаёт онлайн-комнату, участники подключаются к ней со своих телефонов, и все этапы идут в реальном времени по WebSocket. Первое время этот сервер жил у меня дома, на Mac со статическим IP. Для старта этого хватало, для продукта в магазине — уже нет.
Почему дома — плохая идея
Схема была простой: роутер пробрасывает порты 80 и 443 на Mac, там в Docker крутятся лобби и веб-сервер. Снаружи всё работало. Проблемы начинались изнутри:
- Из домашней сети сайт не открывался. Роутер не умеет NAT loopback, а Mac ходит в интернет через VPN — запрос к собственному домену уходил в никуда. Каждый раз хотелось решить, что «сервер упал».
- Продакшен зависел от рабочего ноутбука. Перезагрузка, обновление macOS, сон — и у людей посреди командной сессии отваливается комната.
- Открытые порты на домашней машине. Держать их постоянно неспокойно.
Стек, который переехал без изменений
К счастью, с самого начала всё было упаковано в Docker Compose. Два сервиса:
- lobby — сервер комнат на Node.js 22, Fastify и WebSocket. Хранит имя участника, итоги по стилям, ходы в квестах и отметки коллег; комнаты удаляются сами через сутки.
- caddy — веб-сервер и обратный прокси. Сам выпускает и продлевает сертификаты Let's Encrypt, проксирует WebSocket как есть и раздаёт статические сайты.
Поэтому переезд свёлся к трём шагам: скопировать папку на сервер, выполнить
docker compose up -d --build и переключить DNS. Caddy выпустил сертификаты, как только DNS
начал указывать на новый адрес.
Домены: развести продукт и личное
Заодно я навёл порядок в адресах. Раньше всё жило на одном личном домене, теперь у приложения свой:
discapp.ru— сайт D.I.S.C. и политика конфиденциальности (теперь — rumb-team.online);lobby.discapp.ru— сервер комнат (теперь — lobby.rumb-team.online);timkit.online— этот сайт.
Со сроками повезло: публичной версии приложения ещё не было, поэтому старый адрес лобби я просто удалил. Если бы приложение уже стояло у людей, в нём был бы зашит старый адрес, а обновляются пользователи не сразу. Тогда старый домен пришлось бы оставить рабочим синонимом нового — в Caddy это одна строка:
{$LOBBY_DOMAIN}, {$OLD_LOBBY_DOMAIN} {
reverse_proxy lobby:30070
}
Так же со старой ссылкой на политику конфиденциальности: она могла остаться в карточке магазина, поэтому старый адрес отдаёт постоянный редирект на новый.
Деплой одной командой — и одна ловушка
Обновление сервера — это rsync папки и пересборка контейнеров. Я завернул это
в npm run deploy:server. Но на ревью всплыла неочевидная деталь.
Caddyfile подключён в контейнер как отдельный файл. Обычный
rsyncпишет новый файл рядом и подменяет старый — у файла меняется inode, а контейнер продолжает видеть прежнюю версию. Новые правила просто не применились бы.
Решение — копировать с --inplace, чтобы файл перезаписывался на месте, и после
этого делать caddy reload. Конфиг перечитывается без перезапуска, и живые WebSocket-подключения
участников не рвутся посреди сессии.
Что в итоге
- Сервер не зависит от моего ноутбука и домашней сети.
- У продукта свой домен, у меня — свой сайт, и оба на одном недорогом VPS.
- Старые ссылки на сайт и политику ведут на новый адрес.
- Деплой — одна команда: комнаты переживают перезапуск, приложения переподключаются сами.
Если вы держите pet-проект «на домашнем сервере», упакуйте его в Docker Compose с первого дня. Тогда переезд займёт один вечер.