Control Node

🚀 Continuous Delivery and Orchestration as code for Elixir


def deps do
    {:control_node, "~> 0.3.0"}


control_node is an Elixir library which offers APIs that allows to build custom deployment and orchestration workflows. For a given a release tar of an Elixir/Erlang project control_node offers APIs to store and manage release tar via local registry and deploy releases to remote hosts (via SSH) and monitor service nodes.


In order to use control_node you must ensure the following,

  • You are deploying to virtual machines or bare metal servers. Control node should have SSH access all these host machines where the releases will run.
  • Your Erlang/Elixir project when started should run the EPMD (it runs by default if you don't change the config)


  • [x] Support multiple namespaces for a release
  • [x] Rollout releases to hosts via SSH
  • [x] Native node monitoring and restart on failover
  • [x] Dynamically scale up/down your release instances
  • [x] Native service monitoring/health check
  • [x] Blue-Green deployment
  • [x] Support failover via heart
  • [ ] Support namespace environment variable configuration
  • [ ] Rollback releases

Quick example

This library ships with an example service_app under example/ folder. You can try out this library by trying to deploy the release using the following steps,

Clone the repo

$ git clone
$ cd control-code/

Start an SSH server locally where the release will be deployed,

$ docker-compose up -d

Start iex with distribution turned on

$ iex -S mix
Erlang/OTP 23 [erts-11.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.10.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :net_kernel.start([:control_node_test, :shortnames])

Execute the Elixir code snippets in the console,

  • Define ServiceApp module (copy paste the code in the console) which will offer API to deploy service_app,

    defmodule ServiceApp do
    use ControlNode.Release,
      spec: %ControlNode.Release.Spec{name: :service_app, base_path: "/app/service_app"}
  • Declare a host_spec which will hold the details of which host the release can be deployed to

    host_spec = %ControlNode.Host.SSH{
    host: "localhost",
    port: 2222,
    user: "",
    private_key_dir: Path.join([File.cwd!(), "test/fixture", "host-vm/.ssh"])
  • Declare a namespace_spec which define the namespace for a given release. Notice that the namespace allows specifying a list of hosts and registry. A registry module offers API to retrieve the release tar and here we use a Local registry which will retrieve the release tar from the filesystem.

namespace_spec = %ControlNode.Namespace.Spec{
  tag: :testing,
  hosts: [host_spec],
  registry_spec: %ControlNode.Registry.Local{path: Path.join(File.cwd!(), "example")},
  deployment_type: :incremental_replace,
  • Now we deploy the release to a given namespace_spec i.e. the release we be started on on all the hosts specified in the namespace. Notice that once the deployment is finished control_node_test@hostname automatically connects to release nodes,
{:ok, namespace_manager} = ControlNode.Namespace.start_link(namespace_spec, ServiceApp)
ControlNode.Namespace.deploy(namespace_manager, "0.1.0")

SSH server config to enable tunneling

In order to ensure that Control Node can connect to release node the SSH servers running the release should allow tunneling,

AllowTcpForwarding yes


  • SSH client only supports ed25519 keys
  • Only short names for nodes are allowed ie. sevice_app@hostname is support and not
  • Nodes of a given release (deployed to different) should have different hostname for eg. if node 1 has node name service_app@host1 then another node of service_app should have a different node name.