पाठ 22 / 25

Releases, Configuration and Deployment

Build releases, configure them at run time and deploy to containers and clusters.

Shipping Elixir applications

Elixir applications are deployed as releases: MIX_ENV=prod mix release produces a self-contained directory with your compiled code, dependencies and the Erlang runtime, so the server needs no Elixir installation. Configuration has stages: config/config.exs and environment files are evaluated at build time, while config/runtime.exs is evaluated when the release starts, which is where secrets and environment variables such as DATABASE_URL, SECRET_KEY_BASE and PHX_HOST belong. Phoenix provides mix phx.gen.release --docker to generate a release setup and a multi-stage Dockerfile. Run migrations with a release command such as bin/migrate, which calls a migration module, as part of deployment. Releases support bin/shop start, remote (attach an IEx shell to the running system for debugging) and eval. Clustering connects BEAM nodes so PubSub, Presence and distributed processes work across machines, often using libcluster with DNS or Kubernetes strategies. Platforms such as Fly.io, Gigalixir and Render have Elixir guides, and Kubernetes works well too. Observe production with Telemetry, Phoenix LiveDashboard and OpenTelemetry.

From code to running release

mix release bundles code, dependencies and the runtime; runtime.exs reads configuration at boot.

A source folder flowing into a sealed package box, then into a container shape on a server rack, with a separate small settings card feeding into the container at start.
Figure 8.1 — Building and running a release.

runtime.exs and a release workflow

Secrets come from the environment at boot, not at build time.

# config/runtime.exs
import Config

if config_env() == :prod do
  database_url =
    System.get_env("DATABASE_URL") ||
      raise "environment variable DATABASE_URL is missing"

  config :shop, Shop.Repo,
    url: database_url,
    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

  secret_key_base =
    System.get_env("SECRET_KEY_BASE") ||
      raise "environment variable SECRET_KEY_BASE is missing"

  config :shop, ShopWeb.Endpoint,
    url: [host: System.get_env("PHX_HOST", "example.com"), port: 443, scheme: "https"],
    http: [ip: {0, 0, 0, 0, 0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "4000")],
    secret_key_base: secret_key_base
end

# Shell commands (comments):
#   mix phx.gen.release --docker        # generates Dockerfile, bin/migrate and bin/server
#   MIX_ENV=prod mix assets.deploy
#   MIX_ENV=prod mix release
#   _build/prod/rel/shop/bin/migrate    # run Ecto migrations
#   _build/prod/rel/shop/bin/server     # start the app
#   _build/prod/rel/shop/bin/shop remote   # attach IEx to the running node

Build time versus boot time

Values read in config/prod.exs are baked into the release when it is built. Anything that differs per environment or is secret must be read in config/runtime.exs, or the same image cannot move from staging to production.

त्वरित जाँच: Where should secrets such as DATABASE_URL be read in a release?

  • config/config.exs
  • mix.exs
  • config/runtime.exs, evaluated when the release starts
  • Hard-coded in a module
Answer

config/runtime.exs, evaluated when the release starts — runtime.exs runs at boot, so each environment can supply its own values.