NexusCS

Erlang

Programming
Quick reference for Erlang - a functional language built for massively concurrent, fault-tolerant, distributed systems with hot code swapping.
featured

Getting started

Hello world

-module(hello).
-export([world/0]).

world() ->
    io:format("Hello, World!~n").

Save as hello.erl, compile and run:

$ erlc hello.erl          # Compile to hello.beam
$ erl -noshell -s hello world -s init stop
Hello, World!

REPL basics

$ erl                     # Start Erlang shell
1> 2 + 3.                 % Expression (note the dot)
5
2> f().                   % Forget all bindings
ok
3> halt().                % Exit shell

Variables

X = 10.                   % Assignment (once only)
Y = X + 5.                % Use in expression
_Ignore = expensive().    % _ prefix for unused

Variables are immutable - once bound, cannot change.

Basic types

Numbers

42                        % Integer
3.14                      % Float
2#101                     % Binary (5)
16#FF                     % Hex (255)
$A                        % Character code (65)
1_000_000                 % Underscores for readability

Atoms

ok                        % Simple atom
error                     % Another atom
'Atom with spaces'        % Quoted atom
true                      % Boolean atom
false                     % Boolean atom
undefined                 % Common atom

Atoms start lowercase or are quoted. Used for constants.

Strings and binaries

"Hello"                   % String (list of integers)
<<"Binary">>              % Binary (efficient storage)
[$H, $e, $l, $l, $o]      % String as list

Strings are lists: "ab" == [97, 98]

Data structures

Tuples

{ok, Value}               % Common pattern
{error, Reason}           % Error tuple
{Name, Age, City}         % Fixed-size collection

Lists

[1, 2, 3]                 % List literal
[H|T]                     % Head and tail pattern
[]                        % Empty list
[1, 2 | [3, 4]]           % Cons operator

Maps

#{key => value}           % Create map
#{name => "Alice", age => 30}

M = #{a => 1}.
#{a := V} = M.            % Match existing key (V = 1)
M2 = M#{a := 2}.          % Update existing key
M3 = M#{b => 3}.          % Add new key

Records

-record(person, {name, age, city}).

P = #person{name="Bob", age=25}.
P#person.name             % Access field -> "Bob"
P2 = P#person{age=26}.    % Update field

Records are syntactic sugar over tuples.

Pattern matching

Basic patterns

% Destructuring
{ok, Result} = {ok, 42}.
[First, Second | Rest] = [1, 2, 3, 4].
#{name := Name} = #{name => "Alice", age => 30}.

% Matching in function heads
greet({person, Name}) ->
    io:format("Hello, ~s~n", [Name]).

Pattern matching in case

case Expression of
    {ok, Value} ->
        do_something(Value);
    {error, Reason} ->
        handle_error(Reason);
    _ ->
        default_case()
end.

Guards

max(X, Y) when X > Y -> X;
max(X, Y) -> Y.

is_valid(X) when is_integer(X), X > 0 ->
    true;
is_valid(_) ->
    false.

Guards: is_atom/1, is_integer/1, is_list/1, is_tuple/1, is_binary/1, is_map/1

Functions

Function clauses

-module(math).
-export([factorial/1]).

factorial(0) -> 1;
factorial(N) when N > 0 ->
    N * factorial(N - 1).

Clauses separated by ;, last ends with .

Anonymous functions

Fun = fun(X) -> X * 2 end.
Fun(5).                   % -> 10

% Multi-clause
Abs = fun
    (X) when X >= 0 -> X;
    (X) -> -X
end.

Higher-order functions

lists:map(fun(X) -> X * 2 end, [1, 2, 3]).
% -> [2, 4, 6]

lists:filter(fun(X) -> X > 2 end, [1, 2, 3, 4]).
% -> [3, 4]

lists:foldl(fun(X, Acc) -> X + Acc end, 0, [1, 2, 3]).
% -> 6

List operations

Common functions

lists:reverse([1, 2, 3])  % [3, 2, 1]
lists:sort([3, 1, 2])     % [1, 2, 3]
lists:append([1, 2], [3]) % [1, 2, 3]
lists:flatten([[1], [2]]) % [1, 2]

List comprehensions

[X * 2 || X <- [1, 2, 3]].
% -> [2, 4, 6]

[X || X <- [1, 2, 3, 4], X rem 2 == 0].
% -> [2, 4]

[{X, Y} || X <- [1, 2], Y <- [a, b]].
% -> [{1,a}, {1,b}, {2,a}, {2,b}]

Membership and search

lists:member(2, [1, 2, 3])      % true
lists:keyfind(key, 1, [{key, val}])  % {key, val}
lists:any(fun(X) -> X > 5 end, [1, 2, 3])  % false
lists:all(fun(X) -> X > 0 end, [1, 2, 3])  % true

Processes

Creating processes

% Spawn a process
Pid = spawn(fun() ->
    io:format("In new process~n")
end).

% Spawn with module function
Pid = spawn(module, function, [Args]).

% Spawn and link (die together)
Pid = spawn_link(Fun).

Message passing

% Send message
Pid ! {message, Data}.

% Receive message
receive
    {message, Data} ->
        process_data(Data);
    {other, Info} ->
        handle_other(Info)
after
    5000 ->                     % Timeout in milliseconds
        timeout_handler()
end.

Process info

self()                          % Current process PID
is_process_alive(Pid)          % Check if alive
process_info(Pid)              % Process information
registered()                   % List registered names
whereis(Name)                  % Get PID of registered process

Process patterns

Server loop

-module(counter).
-export([start/0, increment/1, get/1]).

start() ->
    spawn(fun() -> loop(0) end).

loop(Count) ->
    receive
        {increment, From} ->
            From ! {ok, Count + 1},
            loop(Count + 1);
        {get, From} ->
            From ! {value, Count},
            loop(Count)
    end.

increment(Pid) ->
    Pid ! {increment, self()},
    receive {ok, NewCount} -> NewCount end.

get(Pid) ->
    Pid ! {get, self()},
    receive {value, Count} -> Count end.

Process registration

% Register process with name
register(server_name, Pid).

% Send to registered process
server_name ! Message.

% Unregister
unregister(server_name).

Error handling

Try-catch

try
    risky_operation()
catch
    error:Reason ->
        {error, Reason};
    throw:Term ->
        {thrown, Term};
    exit:Reason ->
        {exit, Reason}
after
    cleanup()                   % Always executed
end.

Raising errors

error(Reason)                   % Error exception
throw(Term)                     % Throw exception
exit(Reason)                    % Exit current process
erlang:raise(Class, Reason, Stacktrace)  % Re-raise

Process links and monitors

% Link (bidirectional)
link(Pid).
unlink(Pid).

% Monitor (unidirectional)
Ref = monitor(process, Pid).
demonitor(Ref).

% Trap exits
process_flag(trap_exit, true).
receive
    {'EXIT', Pid, Reason} ->
        handle_exit(Reason)
end.

Module structure

Module declaration

-module(mymodule).              % Module name (matches filename)
-export([func1/1, func2/2]).    % Public functions
-import(lists, [map/2, filter/2]).  % Import functions

-define(TIMEOUT, 5000).         % Macro definition
-record(state, {count, name}).  % Record definition

% Private function (not exported)
helper(X) -> X + 1.

Compilation

$ erlc mymodule.erl             # Compile to .beam
$ erlc +debug_info mymodule.erl # With debug info
$ erlc -o ebin src/mymodule.erl # Output directory

In Erlang shell:

c(mymodule).                    % Compile and load
l(mymodule).                    % Reload module
m().                            % List loaded modules

OTP basics

gen_server behavior

-module(my_server).
-behaviour(gen_server).

-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2,
         handle_info/2, terminate/2, code_change/3]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, #{count => 0}}.

handle_call(get_count, _From, State = #{count := Count}) ->
    {reply, Count, State};
handle_call({increment, N}, _From, State = #{count := Count}) ->
    {reply, ok, State#{count := Count + N}}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

Using gen_server

{ok, Pid} = my_server:start_link().
gen_server:call(Pid, get_count).           % Synchronous
gen_server:cast(Pid, async_message).       % Asynchronous

Common modules

io module

io:format("Hello ~s~n", ["World"])  % Print formatted
io:get_line("Prompt: ")             % Read line
io:read("Enter: ")                  % Read term
io:fwrite("Text~n")                 % Write output

Format specifiers: ~s (string), ~p (pretty), ~w (write), ~n (newline)

lists module

lists:map(Fun, List)
lists:filter(Fun, List)
lists:foldl(Fun, Acc, List)
lists:foldr(Fun, Acc, List)
lists:foreach(Fun, List)
lists:reverse(List)
lists:sort(List)
lists:zip(List1, List2)
lists:unzip(ListOfTuples)

string module

string:length("hello")              % 5
string:concat("Hello", " World")    % "Hello World"
string:substr("hello", 2, 3)        % "ell"
string:tokens("a,b,c", ",")         % ["a", "b", "c"]
string:uppercase("hello")           % "HELLO"
string:lowercase("HELLO")           % "hello"

file module

file:read_file("file.txt")          % {ok, Binary}
file:write_file("file.txt", Data)   % ok | {error, Reason}
file:consult("config.erl")          % Read Erlang terms
file:list_dir(".")                  % List directory
file:delete("file.txt")             % Delete file

timer module

timer:sleep(1000)                   % Sleep 1 second
timer:send_after(1000, Pid, Msg)    % Send after delay
timer:send_interval(1000, Pid, Msg) % Send repeatedly
timer:apply_after(1000, M, F, A)    % Apply after delay

calendar module

calendar:local_time()               % {{Y,M,D}, {H,M,S}}
calendar:universal_time()           % UTC time
calendar:now_to_local_time(Now)     % Convert timestamp

Binary operations

Binary construction

<<1, 2, 3>>                         % 3 bytes
<<X:16>>                            % 16-bit integer
<<X:32/float>>                      % 32-bit float
<<X:8/binary>>                      % 8-byte binary
<<"hello">>                         % Binary string
<<1:1, 0:1, 1:1>>                   % Bits (not bytes)

Binary pattern matching

<<Head:8, Rest/binary>> = <<1, 2, 3>>.
% Head = 1, Rest = <<2, 3>>

<<A:4, B:4>> = <<255>>.
% A = 15, B = 15

<<"GET ", Rest/binary>> = <<"GET /index.html">>.
% Rest = <<"/index.html">>

Macros and includes

Macros

-define(TIMEOUT, 5000).
-define(MAX(X, Y),
    if X > Y -> X; true -> Y end).

% Usage
timer:sleep(?TIMEOUT).
Result = ?MAX(10, 20).

Include files

-include("header.hrl").             % Include file
-include_lib("stdlib/include/ms_transform.hrl").

In header.hrl:

-define(CONST, 42).
-record(rec, {field1, field2}).

Gotchas

Variable binding

% ❌ Variables cannot be rebound
X = 1.
X = 2.                              % Error: no match

% ✅ Use new variable
X = 1.
Y = 2.

% ✅ Use f() to forget in shell
X = 1.
f(X).                               % Forget X binding
X = 2.                              % Now OK

String vs binary

% String is list of integers
"hello" == [104, 101, 108, 108, 111]  % true

% ⚠️ Strings are inefficient for large data
Data = "large text...".             % List (slow)
Data = <<"large text...">>.         % Binary (fast)

Message ordering

% ⚠️ Messages are ordered per sender
% But NOT across different senders
Pid ! msg1.
Pid ! msg2.                         % msg2 arrives after msg1

% ⚠️ Selective receive can reorder
receive
    pattern2 -> ok                  % May receive pattern2 first
after 0 ->
    receive pattern1 -> ok end
end.

Process dictionary

% ⚠️ Avoid process dictionary (global mutable state)
put(key, value).                    % Sets in process dict
get(key).                           % Gets from process dict

% ✅ Use process state or ETS instead

List append performance

% ❌ Inefficient: O(n) per append
List1 = [1, 2, 3].
List2 = List1 ++ [4].               % Copies entire List1

% ✅ Prepend is O(1)
List2 = [4 | List1].                % Fast
lists:reverse([4 | List1]).         % If order matters

Shell commands

Compilation

c(module).                          % Compile module
c(module, [debug_info]).            % With debug info
l(module).                          % Load/reload

Information

m().                                % List modules
m(module).                          % Module info
h(module, function).                % Help
pid(0, 250, 0).                     % Create PID

Process management

i().                                % Process information
regs().                             % Registered processes
processes().                        % All PIDs
exit(Pid, kill).                    % Kill process

Memory and debugging

memory().                           % Memory usage
erlang:system_info(process_count).  % Process count
dbg:tracer().                       % Start tracer
dbg:p(all, c).                      % Trace all calls

Testing with EUnit

Basic test

-module(math_test).
-include_lib("eunit/include/eunit.hrl").

simple_test() ->
    ?assertEqual(4, 2 + 2).

list_test() ->
    ?assert(length([1, 2, 3]) == 3).

Test fixtures

setup_test_() ->
    {setup,
        fun() -> setup_code() end,       % Setup
        fun(Ctx) -> cleanup(Ctx) end,    % Cleanup
        fun(Ctx) -> test_body(Ctx) end   % Test
    }.

Run tests:

$ erlc -o ebin src/*.erl test/*.erl
$ erl -noshell -pa ebin -eval "eunit:test(math_test, [verbose])" -s init stop

Supervisor trees

Basic supervisor

-module(my_supervisor).
-behaviour(supervisor).

-export([start_link/0, init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    SupFlags = #{
        strategy => one_for_one,
        intensity => 10,
        period => 60
    },
    ChildSpecs = [
        #{
            id => worker1,
            start => {worker, start_link, []},
            restart => permanent,
            shutdown => 5000,
            type => worker,
            modules => [worker]
        }
    ],
    {ok, {SupFlags, ChildSpecs}}.

Restart strategies

% one_for_one: Restart only failed child
strategy => one_for_one

% one_for_all: Restart all children if one fails
strategy => one_for_all

% rest_for_one: Restart failed child + those started after it
strategy => rest_for_one

% simple_one_for_one: For dynamic children
strategy => simple_one_for_one

SupFlags map

#{
    strategy => one_for_one,     % Restart strategy
    intensity => 10,             % Max restarts
    period => 60                 % In seconds
}

If intensity restarts happen within period seconds, supervisor terminates.

ChildSpec map

#{
    id => unique_id,             % Child identifier
    start => {M, F, A},          % Start function
    restart => permanent,        % permanent | transient | temporary
    shutdown => 5000,            % Timeout (ms) or brutal_kill
    type => worker,              % worker | supervisor
    modules => [module]          % For hot code upgrades
}

Restart options

restart => permanent    % Always restart
restart => transient    % Restart if abnormal exit
restart => temporary    % Never restart

Dynamic children

% For simple_one_for_one supervisors
supervisor:start_child(SupPid, [Args]).
supervisor:terminate_child(SupPid, ChildPid).
supervisor:which_children(SupPid).
supervisor:count_children(SupPid).

gen_statem

State Functions mode

-module(door).
-behaviour(gen_statem).

-export([start_link/0, open/0, close/0]).
-export([init/1, callback_mode/0, terminate/3]).
-export([locked/3, unlocked/3]).

start_link() ->
    gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, locked, #{}}.

callback_mode() ->
    state_functions.

% State: locked
locked({call, From}, unlock, Data) ->
    {next_state, unlocked, Data, [{reply, From, ok}]};
locked(EventType, EventContent, Data) ->
    {keep_state, Data}.

% State: unlocked
unlocked({call, From}, lock, Data) ->
    {next_state, locked, Data, [{reply, From, ok}]};
unlocked({call, From}, open, Data) ->
    {keep_state, Data, [{reply, From, ok}]}.

terminate(_Reason, _State, _Data) ->
    ok.

Handle Event Function mode

-module(door2).
-behaviour(gen_statem).

-export([init/1, callback_mode/0, handle_event/4]).

init([]) ->
    {ok, locked, #{}}.

callback_mode() ->
    handle_event_function.

handle_event({call, From}, unlock, locked, Data) ->
    {next_state, unlocked, Data, [{reply, From, ok}]};
handle_event({call, From}, lock, unlocked, Data) ->
    {next_state, locked, Data, [{reply, From, ok}]};
handle_event(EventType, EventContent, State, Data) ->
    {keep_state, Data}.

Return values

% Change state
{next_state, NewState, NewData}
{next_state, NewState, NewData, Actions}

% Keep current state
{keep_state, NewData}
{keep_state, NewData, Actions}

% Keep state and data
keep_state_and_data
{keep_state_and_data, Actions}

Actions and timeouts

% State timeout (reset on state change)
{state_timeout, 5000, timeout_event}

% Generic timeout
{timeout, 5000, timeout_event}

% Event timeout (reset on any event)
{{timeout, Name}, 5000, timeout_event}

% Reply action
{reply, From, Reply}

Client API

gen_statem:call(Name, Request).
gen_statem:cast(Name, Event).
gen_statem:stop(Name).

Application behaviour

Application callback module

-module(myapp_app).
-behaviour(application).

-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
    myapp_supervisor:start_link().

stop(_State) ->
    ok.

.app resource file

% src/myapp.app.src
{application, myapp, [
    {description, "My Application"},
    {vsn, "1.0.0"},
    {registered, [myapp_sup]},
    {mod, {myapp_app, []}},
    {applications, [
        kernel,
        stdlib,
        sasl
    ]},
    {env, [
        {port, 8080},
        {host, "localhost"}
    ]},
    {modules, []},
    {licenses, ["Apache-2.0"]},
    {links, []}
]}.

Application control

application:start(myapp).
application:stop(myapp).
application:which_applications().
application:loaded_applications().

% Get environment variable
application:get_env(myapp, port).         % {ok, 8080}
application:get_env(myapp, port, 3000).   % 8080 or default

Start types

start(normal, _Args) ->
    % Normal start
    myapp_supervisor:start_link();
start({takeover, Node}, _Args) ->
    % Taking over from Node
    myapp_supervisor:start_link();
start({failover, Node}, _Args) ->
    % Failover from Node
    myapp_supervisor:start_link().

Distributed Erlang

Starting nodes

# Start with short name (local network)
$ erl -sname node1

# Start with full name (distributed)
$ erl -name node1@hostname.domain

# Set cookie (for authentication)
$ erl -sname node1 -setcookie secret

Node operations

node().                                  % Current node name
nodes().                                 % Connected nodes
nodes(connected).                        % Same as nodes()
nodes(hidden).                           % Hidden nodes

% Connect to node
net_adm:ping('node2@hostname').          % pong | pang

% Spawn on remote node
spawn('node2@hostname', fun() -> code() end).
spawn('node2@hostname', Module, Function, Args).

Magic cookies

% Set cookie at runtime
erlang:set_cookie(node(), secret).
erlang:set_cookie('node2@hostname', other_secret).

% Get cookie
erlang:get_cookie().

Nodes with different cookies cannot communicate.

Monitoring nodes

% Monitor node connection
monitor_node('node2@hostname', true).

receive
    {nodedown, Node} ->
        io:format("Node ~p went down~n", [Node])
end.

% Stop monitoring
monitor_node('node2@hostname', false).

Global name registration

% Register name globally
global:register_name(myserver, Pid).

% Send to global name
global:send(myserver, Message).

% Lookup global name
global:whereis_name(myserver).           % Pid | undefined

ETS (Erlang Term Storage)

Creating tables

% Create table
ets:new(tablename, [set, named_table, public]).

% Table types
ets:new(t, [set]).              % Key-value (unique keys)
ets:new(t, [ordered_set]).      % Ordered keys
ets:new(t, [bag]).              % Duplicate keys, unique values
ets:new(t, [duplicate_bag]).    % Duplicate keys and values

Access control

% Access types
[public]                        % All processes can read/write
[protected]                     % All read, owner writes (default)
[private]                       % Only owner can access

% Named tables
[named_table]                   % Access by name, not Tid

Basic operations

% Insert
ets:insert(tablename, {key, value}).
ets:insert(tablename, [{key1, val1}, {key2, val2}]).

% Lookup
ets:lookup(tablename, key).              % [{key, value}] or []
ets:lookup_element(tablename, key, 2).   % value (crashes if not found)

% Delete
ets:delete(tablename, key).              % Delete entry
ets:delete(tablename).                   % Delete table
ets:delete_all_objects(tablename).       % Empty table

Match operations

% Match pattern
ets:match(tablename, {key, '$1'}).       % [[value1], [value2], ...]
ets:match(tablename, {'$1', value}).     % [[key1], [key2], ...]

% Match object (return full tuples)
ets:match_object(tablename, {key, '_'}). % [{key, val1}, {key, val2}]

% Select with match spec
ets:select(tablename, [{
    {key, '$1'},                         % Pattern
    [],                                  % Guards
    ['$1']                               % Result
}]).

Table information

ets:info(tablename).                     % All info
ets:info(tablename, size).               % Number of entries
ets:info(tablename, memory).             % Memory in words
ets:info(tablename, owner).              % Owner PID

ets:all().                               % All table IDs
ets:tab2list(tablename).                 % Convert to list

Iteration

% First/next iteration
Key = ets:first(tablename).
NextKey = ets:next(tablename, Key).

% Fold
ets:foldl(fun({K, V}, Acc) ->
    [{K, V} | Acc]
end, [], tablename).

Type specifications

Function specs

-spec function_name(Type1, Type2) -> ReturnType.
function_name(Arg1, Arg2) ->
    ...

% Examples
-spec add(integer(), integer()) -> integer().
add(X, Y) -> X + Y.

-spec divide(number(), number()) -> {ok, float()} | {error, atom()}.
divide(_X, 0) -> {error, division_by_zero};
divide(X, Y) -> {ok, X / Y}.

Type definitions

-type name() :: atom().
-type age() :: non_neg_integer().
-type person() :: {name(), age()}.

-type result(T) :: {ok, T} | {error, term()}.

% Opaque type (implementation hidden)
-opaque private_data() :: {internal, term()}.

% Export types
-export_type([person/0, result/1]).

Common built-in types

% Basic types
any()                           % Any type
none()                          % No value
atom()
integer()
float()
number()                        % integer() | float()
binary()
bitstring()
boolean()                       % true | false

% Compound types
list()
list(T)                         % List of T
tuple()
map()

% Special types
pid()
reference()
fun()
fun((T1, T2) -> T3)            % Function type

Advanced type patterns

% Union types
-type status() :: running | stopped | paused.
-type id() :: integer() | binary().

% Record types
-record(user, {name :: string(), age :: integer()}).
-type user() :: #user{}.

% Constrained types
-spec get_user(Id) -> {ok, user()} | {error, not_found}
    when Id :: pos_integer().

Running Dialyzer

# Build PLT (Persistent Lookup Table)
$ dialyzer --build_plt --apps erts kernel stdlib

# Analyze application
$ dialyzer ebin/

# With options
$ dialyzer -r src/ --src
$ dialyzer --no_check_plt ebin/

In Erlang shell:

dialyzer:run([{files, ["ebin/mymodule.beam"]}]).

rebar3 basics

Project creation

# Create application
$ rebar3 new app myapp

# Create library
$ rebar3 new lib mylib

# Create release
$ rebar3 new release myrelease

# Create Escript
$ rebar3 new escript myscript

Common commands

# Compile
$ rebar3 compile

# Run tests
$ rebar3 eunit
$ rebar3 ct                     # Common Test
$ rebar3 do eunit, ct           # Run both

# Start shell
$ rebar3 shell

# Create release
$ rebar3 release

# Clean
$ rebar3 clean

rebar.config

{erl_opts, [
    debug_info,
    warnings_as_errors,
    {i, "include"}
]}.

{deps, [
    {cowboy, "2.10.0"},
    {jsx, "3.1.0"},
    {mochiweb, {git, "https://github.com/mochi/mochiweb.git", {tag, "v2.22.0"}}}
]}.

{plugins, [
    rebar3_hex
]}.

{profiles, [
    {prod, [
        {erl_opts, [no_debug_info, warnings_as_errors]},
        {relx, [{dev_mode, false}]}
    ]},
    {test, [
        {deps, [{meck, "0.9.2"}]}
    ]}
]}.

Dependency management

# Get dependencies
$ rebar3 get-deps

# Upgrade dependencies
$ rebar3 upgrade

# Lock dependencies
$ rebar3 lock

# List dependencies
$ rebar3 tree

Release commands

# Create release
$ rebar3 release

# Run release
$ _build/default/rel/myapp/bin/myapp console

# Create tarball
$ rebar3 tar

External resources

Storage reference

Also see