Как задеплоить Phoenix-приложение без VPS

Elixir-приложение на Phoenix выкладывается в сеть без аренды сервера: вы отдаёте исходники вместе с mix.exs, платформа определяет Elixir, устанавливает зависимости, собирает проект и выдаёт публичную HTTPS-ссылку. Условий три, и они решающие: порт приложение берёт из переменной PORT и слушает 0.0.0.0, SECRET_KEY_BASE лежит в секретах, а не в коде, база подключается по DATABASE_URL. И есть ловушка для Elixir: если приложение идёт скомпилированным релизом, без PHX_SERVER=true оно стартует без ошибок в логах и не слушает ничего. Ниже — путь от папки с кодом до HTTPS-ссылки на Dockbox.

Почему Phoenix-приложение по умолчанию тащат на VPS

Привычный сценарий — длинный чек-лист: арендовать сервер, поставить совпадающие версии Erlang и Elixir, собрать релиз, поднять базу, поставить перед приложением nginx, выпустить сертификат и проследить, чтобы всё вернулось в строй после перезагрузки. Сам Phoenix готов за вечер, а деплой забирает пару выходных.

Версии Erlang и Elixir. Несовпадение версий с сервером — типовая причина падения сборки на чужой машине.

Сборка релиза. mix release собирает релиз под ОС и архитектуру той машины, где он запускается, — то есть на сервере или в контейнере.

Обратный прокси и сертификат. Без nginx приложение не отдаёт HTTPS, а сертификат нужно продлевать каждые три месяца.

Автозапуск. Чтобы всё поднялось после перезагрузки, нужен systemd-юнит.

Оплата независимо от нагрузки. Машина стоит денег все 30 дней, даже если сайт открывают раз в сутки.

Платформа деплоя снимает эту часть: она находит mix.exs, ставит зависимости, собирает проект и отдаёт публичную HTTPS-ссылку. Ни сервера, ни nginx, ни ручного сертификата.

Шаг 1. Отдайте mix.exs и исходники, а не собранный релиз

Сборка идёт на нашей стороне, поэтому в архив кладите исходный код, а в корне должны лежать mix.exs и mix.lock: по ним платформа видит, что перед ней Elixir, и фиксирует версии библиотек. Папки _build и deps в архив не кладите — это результат сборки на вашей машине, они раздувают загрузку. Готовый релиз из _build/prod/rel тоже не нужен, он собирается заново. Ассеты в priv/static не везите: они компилируются при публикации, если в mix.exs есть стандартный алиас assets.deploy.

Локально релиз собирается так, но перед загрузкой это не требуется:

MIX_ENV=prod mix release

Шаг 2. Слушайте порт из PORT, а не 4000

Платформа передаёт порт в переменной PORT, а приложение должно слушать 0.0.0.0, а не localhost. Жёстко прописанный порт 4000 — причина номер один, по которой ссылка не открывается: приложение работает, но не там, куда приходит трафик.

Плохой вариант:

config :my_app, MyAppWeb.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 4000]

Правильный — читать значение из окружения в config/runtime.exs. Это принципиально: runtime.exs выполняется при запуске приложения, а prod.exs — во время сборки, когда секретов ещё нет.

config :my_app, MyAppWeb.Endpoint,
  http: [
    ip: {0, 0, 0, 0},
    port: String.to_integer(System.get_env("PORT") || "4000")
  ]

Шаг 3. SECRET_KEY_BASE и PHX_HOST — в секреты

В production Phoenix отказывается стартовать без SECRET_KEY_BASE — значения, которым подписываются сессии и cookie. Сгенерируйте его на своей машине:

mix phx.gen.secret

Укажите строку в настройках проекта как секрет SECRET_KEY_BASE: платформа хранит его зашифрованным и подставляет переменной окружения, поэтому в репозиторий он не попадает. Если приложение строит абсолютные ссылки или шлёт письма, после первой публикации добавьте PHX_HOST со своим адресом, иначе ссылки продолжат вести на localhost. Тем же секретом подписываются сессии: сменив значение, вы разлогините всех.

Шаг 4. Включите сервер, если приложение идёт релизом

Классическая ловушка Elixir. Когда приложение запускается не через mix phx.server, а как скомпилированный релиз, генераторы Phoenix оставляют в config/runtime.exs условие: веб-сервер поднимается только если задана переменная PHX_SERVER. Без неё приложение стартует без ошибок в логах, ничего не слушает, а ссылка не отвечает — искать причину в коде бесполезно, её там нет. Добавьте в секреты проекта PHX_SERVER=true — и релиз начнёт отвечать: mix phx.server эта переменная не мешает.

Шаг 5. Решите, где живут база и данные

Управляемой базы здесь нет, поэтому путей два. Первый — поднять PostgreSQL рядом как второй сервис в своём docker-compose с именованным томом, иначе данные обнулятся при первом обновлении кода. Второй — взять внешнюю базу и передать строку подключения секретом в DATABASE_URL, которую Phoenix подхватывает без правок в коде:

DATABASE_URL=ecto://app:***@db:5432/app_prod

Миграции Ecto выполняйте при старте приложения: SSH-доступа в контейнер нет и терминала в панели тоже, запустить разовую команду негде. Стандартный модуль Release из mix phx.gen.release содержит функцию migrate/0 — вызовите её до старта приложения. Файлы, которые пишет приложение, держите в постоянной папке /data: всё рядом с кодом пересоздаётся при публикации.

Шаг 6. Загрузите код и получите ссылку

Проект попадает на платформу архивом или импортом репозитория GitHub, в том числе приватного. Ставить Erlang и Elixir у себя и собирать релиз руками не нужно — всё делается на нашей стороне. Логи и статус сборки видны в панели в реальном времени, поэтому ошибка проявляется сразу, а не пустой страницей. После запуска вы получаете публичную HTTPS-ссылку.

Сколько это стоит и как работают контейнеры

Принципиальная разница с VPS: ни администрировать сервер, ни следить за процессом не нужно — контейнеры работают постоянно, без ограничения по часам и без «засыпания». Упавший контейнер платформа перезапускает сама.

Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM. Первый месяц бесплатный. Для одного Phoenix-приложения этого достаточно, лимита по времени работы у тарифа нет.

Pro — 2 490 ₽/мес. Для нескольких проектов — до пяти.

Team — 4 990 ₽/мес — когда команде нужен запас ресурсов и SLA.

Свой Dockerfile поддерживается, но не обязателен: стек платформа определяет по коду. Если он нужен, помните про PHX_SERVER=true.

Частые вопросы

Нужно ли запускать mix release перед загрузкой?

Нет, загружайте исходный код: зависимости устанавливаются и проект собирается на нашей стороне из mix.exs, а mix.lock фиксирует точные версии. Ни Erlang, ни Elixir на вашей машине не нужны. Если у вас есть свой Dockerfile со сборкой релиза, платформа использует его вместо автоматической сборки — и тогда особенно легко забыть про PHX_SERVER.

Phoenix запустился, но сайт не открывается — почему?

Чаще всего приложение слушает не там: порт жёстко прописан как 4000 вместо чтения из PORT, либо endpoint биндится на localhost вместо 0.0.0.0. Вторая частая причина — релиз без PHX_SERVER: приложение работает, а его веб-сервер выключен, и логи остаются чистыми. Третья — настройки читаются в config/prod.exs вместо config/runtime.exs.

Где взять SECRET_KEY_BASE и куда его положить?

Сгенерируйте значение командой mix phx.gen.secret на своей машине. Не вставляйте его в код и не кладите в конфиг, который уедет в репозиторий: передайте его платформе как секрет, и оно подставится в переменную окружения SECRET_KEY_BASE в зашифрованном виде.

Как подключить PostgreSQL и прогнать миграции Ecto?

Возьмите внешнюю базу или поднимите PostgreSQL вторым сервисом в своём docker-compose, а строку подключения передайте секретом в DATABASE_URL. Миграции выполняйте при старте приложения — например, через стандартный модуль Release с функцией migrate/0, который создают генераторы Phoenix. Другого места нет: SSH-доступа в контейнер и терминала в панели нет.

Работает ли LiveView?

Да. Постоянное соединение LiveView идёт через выданную ссылку, дополнительной настройки websocket не требуется. Так же запускается приложение без веб-части — например, очередь Oban или свой процесс с расписанием. Планировщика задач на платформе нет, поэтому расписание держите в приложении.