ElixirSeniorSystem design

Как развернуть приложение Elixir/OTP с помощью releases?

Mix releases собирают самодостаточный архив с BEAM, приложением и конфигом. Команды: mix release, затем _build/prod/rel/app/bin/app start. Конфигурация runtime через Config.Provider или releases config/runtime.exs.

Развёртывание Elixir/OTP с помощью Mix Releases

Mix Releases (добавлены в Elixir 1.9) создают самодостаточный пакет: BEAM runtime, скомпилированные .beam-файлы, конфигурацию и стартовые скрипты. Сервер не требует установки Elixir или Erlang.

Подготовка: mix.exs

defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_app,
      version: "1.0.0",
      releases: [
        my_app: [
          include_executables_for: [:unix],
          steps: [:assemble, :tar]
        ]
      ]
    ]
  end
end

Runtime-конфигурация

Файл config/runtime.exs читается при каждом старте приложения (не при сборке), что позволяет использовать переменные окружения:

# config/runtime.exs
import Config

config :my_app, MyApp.Repo,
  url: System.fetch_env!("DATABASE_URL"),
  pool_size: String.to_integer(System.get_env("POOL_SIZE", "10"))

config :my_app, MyAppWeb.Endpoint,
  secret_key_base: System.fetch_env!("SECRET_KEY_BASE"),
  url: [host: System.fetch_env!("PHX_HOST"), port: 443, scheme: "https"]

Сборка release

# Локальная сборка (dev-режим)
mix release

# Production (обычно в CI/Docker)
MIX_ENV=prod mix release

# Артефакт:
# _build/prod/rel/my_app/
# Tar-архив: _build/prod/my_app-1.0.0.tar.gz

Dockerfile для production

# Dockerfile
FROM elixir:1.16-otp-26-slim AS builder
WORKDIR /app
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
COPY config/ config/
RUN MIX_ENV=prod mix compile
COPY lib/ lib/
RUN MIX_ENV=prod mix release

FROM debian:bookworm-slim AS runner
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/my_app ./
ENV PHX_HOST=example.com SECRET_KEY_BASE=xxx DATABASE_URL=ecto://...
CMD ["bin/my_app", "start"]

Управление запущенным release

# Запуск в foreground
bin/my_app start

# Daemon
bin/my_app daemon

# Remote IEx shell к запущенному узлу
bin/my_app remote

# Eval (миграции БД)
bin/my_app eval "MyApp.Release.migrate()"

# Graceful stop
bin/my_app stop

Запуск миграций Ecto без Mix

# lib/my_app/release.ex
defmodule MyApp.Release do
  def migrate do
    {:ok, _} = Application.ensure_all_started(:my_app)

    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  defp repos do
    Application.fetch_env!(:my_app, :ecto_repos)
  end
end

Подводные камни

  • Release собирается под конкретную ОС и версию BEAM — собранный на macOS артефакт не запустится на Linux; всегда собирайте в Docker с целевой ОС.
  • config/config.exs и config/prod.exs вычисляются во время компиляции, а не старта — переменные окружения там не доступны; используйте только config/runtime.exs.
  • System.fetch_env!/1 в runtime.exs упадёт при старте, если переменная не задана — это желаемое поведение, но неожиданное в staging без полного .env.
  • Флаг :tar в steps создаёт архив, но не удаляет несжатую директорию — в CI занимает лишнее место.
  • Remote shell через bin/app remote требует совпадения cookie между строящим и запущенным узлом.
  • include_executables_for: [:unix] не создаёт Windows .bat скрипты — важно при cross-platform CI.
  • Mix.install/1 недоступен в release — все зависимости должны быть в mix.exs.
  • Горячее обновление кода (appup/relup) совместимо с releases, но требует ручной генерации appup-файлов; ошибка в appup может привести к partial upgrade и несогласованному состоянию.

Common mistakes

  • Сводить otp releases к названию метода без lifecycle и failure path.
  • Игнорировать модель runtime: Elixir компилируется в BEAM bytecode и наследует процессы, message passing, supervision и hot-code friendly модель Erlang VM.
  • Не отделять validation, authorization, transaction boundary и business logic.
  • Не обсуждать idempotency, retries, shutdown и observability.

What the interviewer is testing

  • Объясняет otp releases через конкретную точку lifecycle в Elixir.
  • Приводит корректный минимальный пример без вымышленных методов или callbacks.
  • Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.
  • Связывает решение с метриками, backpressure, retry policy и graceful shutdown.

Sources

Related topics