Erlang
gen_server
rebar3
Node Hosting
OTP Applications

How to host a gen_server as a standalone node using rebar3 release?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A gen_server does not run as a “standalone node” by itself. It runs inside an OTP application, and that application is packaged into a release that boots an Erlang node with the right applications started. So the real task is to build a small OTP application around the server and then let rebar3 release produce a runnable node.

Start with an OTP Application, Not Only a Module

A gen_server module is only one process implementation. To host it properly, you also need:

  • an application module,
  • a supervisor,
  • release configuration,
  • and a start command that boots the release.

A minimal project can be created with rebar3 new app.

bash
rebar3 new app name=myserver
cd myserver

That gives you the OTP application structure that a release expects.

Put the gen_server Under a Supervisor

A release should not start an orphan process manually in the shell. It should start the application, and the application should start a supervisor, which then starts the gen_server.

A simple server module might look like this.

erlang
1-module(myserver_worker).
2-behaviour(gen_server).
3
4-export([start_link/0, get_value/0, set_value/1]).
5-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
6
7start_link() ->
8    gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
9
10get_value() ->
11    gen_server:call(?MODULE, get_value).
12
13set_value(Value) ->
14    gen_server:call(?MODULE, {set_value, Value}).
15
16init(InitialValue) ->
17    {ok, InitialValue}.
18
19handle_call(get_value, _From, State) ->
20    {reply, State, State};
21handle_call({set_value, Value}, _From, _State) ->
22    {reply, ok, Value};
23handle_call(_Request, _From, State) ->
24    {reply, {error, unknown_call}, State}.
25
26handle_cast(_Msg, State) ->
27    {noreply, State}.
28
29handle_info(_Info, State) ->
30    {noreply, State}.
31
32terminate(_Reason, _State) ->
33    ok.
34
35code_change(_OldVsn, State, _Extra) ->
36    {ok, State}.

Then add a supervisor that starts it.

erlang
1-module(myserver_sup).
2-behaviour(supervisor).
3
4-export([start_link/0]).
5-export([init/1]).
6
7start_link() ->
8    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
9
10init([]) ->
11    Child = #{
12        id => myserver_worker,
13        start => {myserver_worker, start_link, []},
14        restart => permanent,
15        shutdown => 5000,
16        type => worker,
17        modules => [myserver_worker]
18    },
19    {ok, {{one_for_one, 1, 5}, [Child]}}.

That structure gives you a real OTP process tree rather than a one-off shell process.

Make Sure the Application Starts the Supervisor

Your application callback module should hand control to the supervisor.

erlang
1-module(myserver_app).
2-behaviour(application).
3
4-export([start/2, stop/1]).
5
6start(_StartType, _StartArgs) ->
7    myserver_sup:start_link().
8
9stop(_State) ->
10    ok.

This is the point where the release and the gen_server connect. When the application starts, the supervisor starts. When the supervisor starts, the gen_server starts.

Configure the Release in rebar.config

rebar3 needs to know which application belongs in the release.

erlang
1{erl_opts, [debug_info]}.
2
3{relx, [
4    {release, {myserver, "0.1.0"}, [myserver]},
5    {dev_mode, false},
6    {include_erts, true}
7]}.

Then build the release.

bash
rebar3 release

The runnable node lands under _build/default/rel/myserver/.

Run the Node and Interact with It

Start the release in the foreground first. It is easier to debug that way.

bash
_build/default/rel/myserver/bin/myserver foreground

For an interactive shell, use:

bash
_build/default/rel/myserver/bin/myserver console

Once the node is up, you can call the registered server from the Erlang shell.

erlang
myserver_worker:set_value(42).
myserver_worker:get_value().

That is what “hosting the gen_server as a standalone node” really means: the release boots a self-contained node that starts your OTP application automatically.

Common Pitfalls

  • Treating a gen_server module as deployable without wrapping it in an OTP application and supervisor.
  • Starting the server manually in the shell instead of from the application start callback.
  • Forgetting to include the application in the release definition.
  • Testing only in console mode and not verifying that the release boots correctly in foreground mode.
  • Skipping supervision and then losing the resilience benefits that OTP is supposed to provide.

Summary

  • A gen_server is hosted properly by packaging an OTP application into a rebar3 release.
  • The correct structure is application -> supervisor -> gen_server.
  • 'rebar3 release creates a runnable node under _build/default/rel/....'
  • Use foreground or console to start and inspect the node.
  • The release is the standalone unit, not the gen_server module by itself.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions