A Mix release is an artifact: a tarball containing the BEAM runtime, every compiled BEAM file, every dep, and a boot script. The machine running production does not need Elixir installed. It does not need mix. It does not need deps.get. It unpacks the tarball and runs the binary. That single property β the production server is not a build server β is where most βmysterious production bugβ stories dissolve.
#The baseline pipeline
# Build
MIX_ENV=prod mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release
# Test the release, not the dev code
_build/prod/rel/prismatic/bin/prismatic eval 'Prismatic.SmokeTest.run()'
# Ship
tar -czf prismatic.tar.gz -C _build/prod/rel prismatic
scp prismatic.tar.gz prod:/opt/
ssh prod 'tar -xzf /opt/prismatic.tar.gz && /opt/prismatic/bin/prismatic start'The βshipβ step is literally moving bytes. Nothing on the prod host touches source.
#Mistake 1: compile-time config that depends on runtime env
# β Captured at build time β baked into the release
config :prismatic, :api_url, System.get_env("API_URL")The release is built once with whatever API_URL was set at build time. On the prod host, API_URL might be different β and the release will not notice. The fix is config/runtime.exs:
# β
Evaluated at boot on the prod host
import Config
config :prismatic, :api_url, System.fetch_env!("API_URL")Put every environment-dependent setting in runtime.exs. Not prod.exs. Not config.exs. runtime.exs.
Mistake 2: Mix.env/0 at runtime
Mix.env/0 returns nil in a release because mix is not loaded. Code that branches on Mix.env/0 silently takes the nil branch in prod. Every crash report eventually traces back to this one line.
The fix: move the branch to compile time using @env Mix.env():
@env Mix.env()
if @env == :prod, do: ... # resolved at compile time, safe at runtimeBetter yet: put the behavior in config and read config at runtime.
#Mistake 3: assets not bundled in the release
Phoenix releases need mix assets.deploy BEFORE mix release. If you skip it, the release ships with no compiled CSS/JS and the running server 404s on /assets/app.css. The fix is boring: run mix assets.deploy in CI before the release step. The :releases config block in mix.exs can enforce it via a steps: [&assets_check/1, :assemble] hook.
#Rolling vs blue-green
For Prismatic, blue-green beats rolling β new release starts on a new VM, health check passes, traffic cuts over, old VM shuts down. Rolling restarts in-place save VM cost but add βwhat version is process 12345 running?β questions to every incident. Blue-green makes the answer obvious.
#Where to go next
- Academy: {{ cross_link(path=β/academy/learn/development-workflowβ, text=βDevelopment Workflowβ) }} β the release in CI
- Glossary: Release, OTP Release, Deployment, Mix, Docker
What ships is what you tested β if you configured the release correctly. Three mistakes break that guarantee. Avoid all three.