BeamDeploy
BeamDeploy is a small Elixir library for blue-green release swaps and
in-process hot upgrades on a single host.
Attribution
Most of the implementation is adapted from fly_deploy. BeamDeploy repackages those ideas for a small single-host library and example app.
Status
BeamDeploy is experimental. The blue-green path has basic release-level integration coverage, and the hot-upgrade path is intended only for compatible code-only updates that you have tested against your own release.
For blue-green swaps, it keeps a long-lived parent BEAM process running locally
and serves your application from a child peer node started with OTP's :peer
module. When you hand it a new mix release tarball, it:
- extracts the release to a temp directory
- boots a new peer with the new code and release config
-
overlaps old and new listeners via
SO_REUSEPORT - gracefully shuts down the old peer
There is no storage backend, polling loop, Docker integration, or platform coupling in this package. You bring the release and decide when to call the upgrade command.
Example Phoenix App
A self-deploying Phoenix demo lives in example/. It is a small
LiveView app with no database that rebuilds itself with a new compile-time label,
creates a fresh release tarball, and swaps to it through BeamDeploy.upgrade/1.
Run it with:
cd example
./start.shThen open http://localhost:4000 and click one of the release cards. You can change the initial label or port with environment variables:
PORT=4010 DEMO_BUTTON_LABEL="Ship the green build" ./start.shIntegration
defmodule MyApp.Application do
use Application
def start(type, args) do
BeamDeploy.start_link(
otp_app: :my_app,
start: {__MODULE__, :start_app, [type, args]},
endpoint: MyAppWeb.Endpoint
)
end
def start_app(_type, _args) do
children = [
MyApp.Repo,
MyAppWeb.Endpoint
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
endEnable BeamDeploy only where you want the parent/peer model:
config :beam_deploy, enabled: trueor:
export BEAM_DEPLOY=true
Outside that environment, BeamDeploy.start_link/1 just calls your start_app
callback directly.
Blue-Green Upgrading
Copy a standard mix release tarball onto the target host, then call:
BeamDeploy.upgrade("/tmp/my_app-0.2.0.tar.gz")Typical release command:
bin/my_app rpc 'BeamDeploy.upgrade("/tmp/my_app-0.2.0.tar.gz")'Useful status helpers:
BeamDeploy.status()
BeamDeploy.peer_node()
BeamDeploy.upgrading?()Hot Upgrading
For compatible code changes, you can reload code inside the current node without the parent/peer runtime:
BeamDeploy.hot_upgrade("/tmp/my_app-0.2.0.tar.gz", otp_app: :my_app)
BeamDeploy.hot_upgrade("/tmp/my_app-0.2.0.tar.gz", otp_app: :my_app, suspend_timeout: 3_000)The hot upgrade path:
- extracts the tarball to a temp directory
-
copies changed
.beamfiles into the currently loaded code paths - loads brand new modules explicitly for embedded-mode releases
- reloads consolidated protocol beams
-
suspends affected processes, runs
code_change/3, and resumes them
Use hot upgrade only when the supervision tree shape is unchanged and the
running release can safely migrate state through code_change/3.
Requirements
-
The runtime node must be distributed (
--nameor--sname). - The new release must be built with the same OTP version as the running node.
-
For Phoenix/Bandit cutovers, pass your endpoint or follow the
MyAppWeb.Endpointnaming convention so BeamDeploy can injectSO_REUSEPORT.
Hot Upgrade Limits
Hot upgrade is not supported for:
- supervision tree topology changes
- Erlang/OTP upgrades
- NIF upgrades
- major runtime config topology changes
Use a cold deploy or the blue-green path for those cases.