Lesson 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.
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 nodeBuild 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.
Quick check: 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.