Malte Krupa


A rc Script For Elixir On FreeBSD - 2023-06-13

The issue

When I deployed my first elixir based application to a FreeBSD jail I had some issues stopping the service. Starting worked like a charm, but whenever I tried to stop it, the following error appeared:

# service app stop
app not running? (check /var/run/app/app.pid).

This was confusing to me, because the process identifier (PID) in the mentioned file was correct and when I tried to start the process again (while it was already running) I received this error:

# service app start
Starting app.
daemon: process already running, pid: 74473
/usr/local/etc/rc.d/app: WARNING: failed to start app

Something was off and after reading some documentation I found out what it was.

Background

Starting an elixir application is usually done by executing a script. I am using the Phoenix Framework which creates this script using the mix phx.gen.release command. The way I designed the deployment, the generated script eventually lands at the following location on a FreeBSD host: /usr/local/app/v1.2.3/bin/server.

When executed, the script will start the erlang virtual machine (BEAM) with a very long command. It might look something like this:

/usr/local/app/v1.2.3/erts-13.2/bin/beam.smp -- -root /usr/local/app/v1.2.3 -bindir /usr/local/app/v1.2.3/erts-13.2/bin -progname erl -- -home /home/app -- -noshell -s elixir start_cli -mode embedded -setcookie I_AM_A_COOKIE -sname app -config /usr/local/app/v1.2.3/releases/v1.2.3/sys -boot /usr/local/app/v1.2.3/releases/v1.2.3/start -boot_var RELEASE_LIB /usr/local/app/v1.2.3/lib -- -extra --no-halt

Somehow we need to make the rc script aware of this long command. Or at least parts of it.

The solution

Assuming our service is called app, we will place the following rc script at /usr/local/etc/rc.d/app.

#!/bin/sh

# PROVIDE: app
# REQUIRE: LOGIN DAEMON NETWORKING
# KEYWORD: shutdown

. /etc/rc.subr

name="app"
rcvar="app_enable"

command="/usr/sbin/daemon"
pidfile="/var/run/app/${name}.pid"

task="/usr/local/app/v1.2.3/bin/server"
procname="*beam.smp*"

app_chdir="/usr/local/app"

command_args="-p ${pidfile} -t ${name} -u app -o /var/log/app.log ${task}"

load_rc_config $name
run_rc_command "$1"

To get a general understanding of rc files I can recommend the “Practical rc.d scripting in BSD”-Guide.

The important part for an elixir application and this scenario is the combination of command, command_args, task and procname.

You might already guess the problem: command contains the script which is created by mix phx.gen.release. And procname must contain the command which is eventually created by the script in command.

When you do not set procname to e.g. *beam.smp* you can still run service app start to start the service, but running service app stop will fail because it will check two things:

The first check will succeed because the PID exists. But the second check will fail because the process name generated to run the BEAM is different from the value in command and - if not set - procname.

To solve this issue, I set procname to a globbed string containing beam.smp which works fine. It might become an issue when you have multiple BEAM processes fighting for PIDs.

Should this becomes an issue, try to make the procname more restrictive like e.g.: /usr/local/app/v1.2.3/erts-13.2/bin/beam.smp*.

Finally, to enable this service, add the following line to /etc/rc.conf:

app_enable="YES"

Now you can start and stop your elixir application using the usual service app <start|stop|restart> commands.


Privacy Policy | Imprint