BeamDeploy (BeamDeploy v0.1.0)

Copy Markdown View Source

Blue-green release swaps and in-process hot upgrades for a single Elixir node.

BeamDeploy keeps a long-lived parent node running locally and serves traffic from a child peer node started with OTP's :peer module. When you hand the parent a new mix release tarball, it boots a new peer with the new release, lets both peers overlap on the same socket via SO_REUSEPORT, then gracefully shuts down the old one.

The package is intentionally small:

  • no storage, polling, or orchestration layer
  • no Docker or platform coupling
  • local tarball input only

You copy a release tarball onto the host and call either:

Integration

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
end

Enable parent/peer mode in production with either:

config :beam_deploy, enabled: true

or:

BEAM_DEPLOY=true

In environments where BeamDeploy is not enabled, start_link/1 and start_link/2 just call your start_app MFA directly.

Upgrades

BeamDeploy.upgrade("/tmp/my_app-0.2.0.tar.gz")
# => :ok

BeamDeploy.hot_upgrade("/tmp/my_app-0.2.0.tar.gz", otp_app: :my_app)
# => :ok

The tarball must be a standard mix release archive built with the same OTP version as the running node.

Hot upgrades are only safe for compatible code changes. Use a cold deploy or the blue-green path instead when changing supervision tree shape, upgrading Erlang/OTP, changing major runtime topology, or touching NIFs.

Summary

Functions

Returns true when BeamDeploy parent/peer mode is enabled for this runtime.

Returns all handoff data as a map.

Reads handoff data from the parent node.

Performs an in-process hot upgrade from a local release tarball path.

Returns the incoming replacement peer for the current peer, if one exists.

Returns the outgoing peer being replaced by the current peer, if one exists.

Returns the active peer node, or nil when BeamDeploy is not running.

Stores handoff data that survives peer transitions.

Starts BeamDeploy without parent-level children.

Starts BeamDeploy with optional parent-level children.

Returns the current blue-green status map.

Performs a blue-green upgrade from a local release tarball path.

Returns true if a release swap is currently running.

Functions

enabled?()

@spec enabled?() :: boolean()

Returns true when BeamDeploy parent/peer mode is enabled for this runtime.

get_all_handoff()

@spec get_all_handoff() :: map()

Returns all handoff data as a map.

get_handoff(key)

@spec get_handoff(term()) :: term() | nil

Reads handoff data from the parent node.

hot_upgrade(tarball_path, opts)

@spec hot_upgrade(
  Path.t(),
  keyword()
) :: :ok | {:error, term()}

Performs an in-process hot upgrade from a local release tarball path.

This path does not depend on the parent/peer runtime. It reloads code inside the current node, suspends affected processes, runs code_change/3, and resumes them.

Supported changes are limited to compatible hot-code updates built with the same Erlang/OTP version. NIF upgrades are skipped.

incoming_peer()

@spec incoming_peer() :: node() | nil

Returns the incoming replacement peer for the current peer, if one exists.

outgoing_peer()

@spec outgoing_peer() :: node() | nil

Returns the outgoing peer being replaced by the current peer, if one exists.

peer_node()

@spec peer_node() :: node() | nil

Returns the active peer node, or nil when BeamDeploy is not running.

put_handoff(key, value)

@spec put_handoff(term(), term()) :: :ok

Stores handoff data that survives peer transitions.

start_link(opts)

@spec start_link(keyword()) :: Supervisor.on_start()

Starts BeamDeploy without parent-level children.

start_link(children, opts)

@spec start_link(
  [Supervisor.child_spec()],
  keyword()
) :: Supervisor.on_start()

Starts BeamDeploy with optional parent-level children.

Parent children run on the long-lived parent node before the peer manager. This is useful for services that should not restart on every cutover.

status()

@spec status() :: %{active_node: node() | nil, upgrading: boolean()}

Returns the current blue-green status map.

upgrade(tarball_path)

@spec upgrade(Path.t()) :: :ok | {:error, term()}

Performs a blue-green upgrade from a local release tarball path.

The call can be made from either the parent or active peer node.

upgrading?()

@spec upgrading?() :: boolean()

Returns true if a release swap is currently running.