Elixir: OTP Applications & Releases
The Application Module
# mix new my_app --sup generates exactly this shape.
defmodule MyApp.Application do
# The application callback: the BEAM calls start/2 when the app boots.
use Application
@impl true
def start(_type, _args) do
children = [
# A supervisor module from the previous lesson...
MyApp.Supervisor,
# ...or an inline child spec for simple cases.
{MyApp.Cache, ttl: :timer.minutes(5)}
]
# :rest_for_one — if MyApp.Supervisor dies, the Cache restarts too,
# because it was started after it and may depend on it.
Supervisor.start_link(children, strategy: :rest_for_one, name: MyApp.Sup)
end
end
In mix.exs, the application/0 function wires it up:
[mod: {MyApp.Application, []}, extra_applications: [:logger]]. From then on
mix run --no-halt (dev) or the release script (prod) boots the whole tree.
Application Environment
The app env is a per-application keyword store — the idiomatic place for settings that change per
environment. Read it with Application.get_env/3 (or the compile-time
Application.compile_env/3).
# config/config.exs — compile-time defaults
import Config
config :my_app, :cache, ttl_minutes: 5
# config/runtime.exs — evaluated at RELEASE boot, so secrets and
# environment-specific values belong here, not in config.exs.
config :my_app, :cache,
ttl_minutes: String.to_integer(System.get_env("CACHE_TTL_MIN") || "5")
# Anywhere in code:
Application.get_env(:my_app, :cache)[:ttl_minutes]
Distinguish the two moments carefully: config.exs is baked into the release artifact; runtime.exs is read when the node starts. A value used at compile time (a macro, a module attribute) cannot be overridden by a release-time variable — a classic deploy surprise.
Releases for Deployment
mix release assembles the application plus runtime into a directory whose
bin/ script is the ops interface. The pieces that matter operationally:
# config/runtime.exs already holds the env-driven settings.
MIX_ENV=prod mix release
# Release environment variables (the important ones):
# RELEASE_NODE=name@host distribution name (next lesson)
# RELEASE_COOKIE=secret cluster secret — treat as a credential
# RELEASE_DISTRIBUTION=none|name|full
_build/prod/rel/my_app/bin/my_app daemon
_build/prod/rel/my_app/bin/my_app remote # shell into the RUNNING node
bin/my_app remote deserves emphasis: it connects an iex session to a live
production node, where you can call functions, inspect processes, or hotfix — the same
IEx tools, pointed at production. Combine with
:observer remotely for a full GUI.
Library Applications
Libraries are applications too, usually without a tree: they ship mod: false (no boot
callback) and provide child_specs that the host app mounts in its own supervision
tree. That is why adding {Finch, name: MyFinch} to a children list is the standard way
to configure a library.
# A library exposing a child spec for hosts to mount:
defmodule MyLibrary do
def child_spec(opts) do
%{
id: {__MODULE__, opts[:name]},
start: {MyLibrary.Server, :start_link, [opts]},
type: :worker
}
end
end
Practice
- Create
mix new boots --supand trace the boot order of the generated tree withLoggerlines. - Move one setting from
config.exstoruntime.exs, build a release, and verify the value at runtime. - Attach
bin/boots remoteto a running release and call a function in it.
Next: Distributed Elixir