В прошлом месяце один MacBook Pro из команды использовали как «временный бэкенд»: на нём крутился Swift Vapor-сервис для локальной отладки API iOS-приложения — запустили через swift run и боялись закрыть терминал. В итоге автоматический перезапуск системы после обновления тихо положил сервис на полдня, а QA-инженер, не дождавшись ответа от API, грешил на собственную сеть. От такого «ручного демона» пришлось отказаться сразу после того, как арендовали облачный Mac mini, работающий без выключения — в этой статье описан полный путь: от сервиса в терминале до постоянного процесса на launchd и релизов без простоя.
Сценарий: зачем переносить Vapor-сервис на облачный Mac mini
Swift Vapor часто используют как «лёгкий бэкенд для iOS-клиента»: фейковая авторизация, обработка push-колбэков, приём аналитики, внутренняя админка. Нагрузка небольшая, но требуется доступность 24/7, причём именно на реальном macOS-окружении (иногда нужно тестировать сертификаты APNs или собирать нативные зависимости через swift build). Облачный Mac mini — это выделенная физическая машина, а не виртуалка, и он ровно закрывает эту нишу: локальный компьютер нельзя держать включённым постоянно, а Linux в облаке не соберёт macOS-специфичные зависимости. Но получив в своё распоряжение отдельный «железный» хост, вы забираете на себя и всю ответственность за управление процессами — платформа не будет автоматически перезапускать упавший процесс, это нужно настроить самому через launchd.
Подготовка окружения: инструментарий и распределение портов
После входа сразу проверьте версии инструментов, чтобы избежать несовпадения окружений и, как следствие, разного поведения собранных бинарников:
swift --version
xcode-select -p
mkdir -p ~/apps/vapor-api/releases
mkdir -p ~/apps/vapor-api/logs
При планировании портов сразу зарезервируйте два — это понадобится для дальнейшего переключения без простоя:
| Назначение | Порт | Описание |
|---|---|---|
| Основной продакшн-порт | 8080 | Текущая активная версия, на которую смотрит Nginx |
| Порт для новой версии (staging) | 8081 | Новая сборка сначала проходит самопроверку здесь |
| Внутренняя проверка здоровья | 8080/8081 /healthz |
Кастомный роут Vapor, возвращает версию сборки |
Роут /healthz лучше сразу настроить так, чтобы он возвращал короткий хеш Git-коммита — это позволит визуально убедиться, что при релизе переключение произошло на нужную версию, а не гадать, реально ли перезапустился процесс.
Постоянный процесс через launchd вместо nohup
Создайте файл ~/Library/LaunchAgents/com.m4rent.vaporapi.plist со следующим содержимым:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.m4rent.vaporapi</string>
<key>ProgramArguments</key>
<array>
<string>/Users/deploy/apps/vapor-api/current/Run</string>
<string>serve</string>
<string>--hostname</string>
<string>127.0.0.1</string>
<string>--port</string>
<string>8080</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/deploy/apps/vapor-api/current</string>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/deploy/apps/vapor-api/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/deploy/apps/vapor-api/logs/stderr.log</string>
</dict>
</plist>
Загрузка и проверка:
launchctl load ~/Library/LaunchAgents/com.m4rent.vaporapi.plist
launchctl list | grep vaporapi
curl -s localhost:8080/healthz
Разбор ключевых полей plist
Если KeepAlive установлен в true, launchd автоматически перезапустит процесс при аварийном завершении — отдельный скрипт-«сторож» не нужен. Но будьте внимательны: если сама конфигурация содержит ошибку и процесс падает сразу при старте, launchd уйдёт в цикл «перезапуск каждую секунду» — в этом случае количество перезапусков в launchctl list подскажет причину быстрее, чем анализ логов. RunAtLoad гарантирует автозапуск при загрузке системы или входе пользователя, что вместе с работой облачного Mac mini без плановых простоев в течение года практически исключает необходимость вручную запускать сервис. WorkingDirectory обязательно указывайте явно — иначе относительные пути к конфигам и логам могут не совпасть с ожиданиями, так как рабочая директория launchd по умолчанию не та, что вы предполагаете.
Релиз без простоя: два порта + переключение через Nginx
При релизе новой версии не перезаписывайте директорию current и не перезапускайте тот же процесс на месте — в этом случае неизбежно возникнет окно недоступности в несколько секунд. Вместо этого используйте схему «новый процесс на новом порту, переключение трафика после проверки здоровья»:
cp -R releases/build-2026-07-12 releases/build-2026-07-12-verify
sed -i '' 's/8080/8081/' com.m4rent.vaporapi-staging.plist
launchctl load com.m4rent.vaporapi-staging.plist
curl -s localhost:8081/healthz
После того как /healthz вернёт ожидаемый хеш коммита, переключайте upstream в Nginx:
upstream vapor_api {
server 127.0.0.1:8081;
}
Команда nginx -s reload только перечитывает конфигурацию и не рвёт уже установленные соединения; старый процесс на порту 8080 может корректно завершить обработку текущих запросов, после чего его аккуратно останавливают через launchctl unload.
Ни в коем случае не останавливайте старый порт до переключения трафика — прохождение health-check не гарантирует, что новая версия справится с реальной нагрузкой. Оставьте хотя бы несколько минут на наблюдение: при обнаружении проблемы откат на старый upstream одной командой обойдётся гораздо дешевле, чем откат постфактум.
Логи и мониторинг: не дать диску забиться
Файлы stdout.log/stderr.log, куда launchd перенаправляет вывод, по умолчанию не ротируются — за несколько месяцев работы сервиса они могут вырасти до нескольких гигабайт. Добавьте правило для встроенного newsyslog:
/Users/deploy/apps/vapor-api/logs/stdout.log deploy:staff 644 7 10240 * N
Это правило означает ротацию при превышении 10 МБ и хранение не более 7 архивных файлов. Объём SSD на облачном Mac mini фиксирован, и вышедшие из-под контроля логи прежде всего съедают место, нужное для сборочных артефактов и снапшотов — рекомендуется раз в неделю смотреть на динамику через du -sh ~/apps/vapor-api/logs.
Список типичных проблем
- Пропадают переменные окружения: процесс, запущенный launchd, не наследует переменные, которые вы
export-ировали в.zshrc. Строку подключения к базе данных и подобные настройки нужно прописывать в словареEnvironmentVariablesв plist либо явно загружать из отдельного файла.envв коде. - Занятость порта не обнаруживается:
launchctl loadсообщает, что сервис «уже существует», но подключиться к порту не получается — как правило, это «зомби»-запись plist, оставшаяся после предыдущего аварийного завершения. Сначала выполнитеlaunchctl remove, затем повторитеload. - Цикл перезапусков из-за KeepAlive: ошибка в пути конфигурации приводит к моментальному падению процесса, а
KeepAliveзаставляет его бесконечно перезапускаться, резко нагружая CPU. Сначала остановите процесс черезlaunchctl unload, а затем разбирайтесь в причине. - В скрипте релиза нет точки для отката: директорию
currentлучше делать символической ссылкой на конкретную папкуreleases/build-*. Откат в этом случае — это просто перенаправление ссылки на предыдущую версию с последующимreload, а не перезапись файлов «поверх».
Часто задаваемые вопросы
Почему не запустить Vapor просто через nohup или screen?
nohup только отвязывает процесс от терминала, но не перезапускает его после сбоя или перезагрузки. launchd — системный супервизор macOS, который через KeepAlive автоматически перезапускает процесс и умеет стартовать сервис при загрузке, что важно для арендованной на долгий срок машины.
Обязательно ли использовать два порта для релиза без простоя?
Перезапуск на том же порту всегда оставляет паузу, во время которой соединения обрываются, пока новый процесс не запустится. Запуск новой версии на втором порту, проверка здоровья и переключение upstream в Nginx позволяют старому процессу доработать текущие соединения, пока новый трафик идёт на здоровый инстанс.
Стоит ли держать базу данных на том же Mac mini?
Для небольших нагрузок достаточно локального PostgreSQL или SQLite с ежедневными снапшотами. При больших объёмах данных или нескольких инстансах лучше подключаться к отдельной управляемой базе, оставив за Mac mini только уровень приложения.
Проверьте на выделенном Mac mini
Аренда по дням, права root, доступ за считанные минуты — сначала протестируйте, потом решайте насчёт долгосрочной аренды.