Deploying a Nova Application#
In development we have been using rebar3 nova serve which gives us hot-reloading and debug logging. For production we need to build a proper OTP release. In this article we will look at how releases work, production configuration, and how to deploy a Nova application.
Releases#
An OTP release is a self-contained package that includes your application, all dependencies, and optionally the Erlang runtime itself. This means you can deploy to a server that doesn’t even have Erlang installed.
Rebar3 uses relx to build releases. If you look at the rebar.config that was generated when we created our Nova app, you will see the release configuration:
{relx, [{release, {my_first_nova, "0.1.0"},
[my_first_nova,
sasl]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true},
{sys_config_src, "config/dev_sys.config.src"},
{vm_args_src, "config/vm.args.src"}
]}.This is the development release config. It uses dev_mode which means it symlinks to your source instead of copying files, and it does not include ERTS (the Erlang runtime).
Production profile#
We override these settings for production using a rebar3 profile. Add this to your rebar.config if it is not already there:
{profiles, [
{prod, [
{relx, [
{dev_mode, false},
{include_erts, true},
{sys_config_src, "config/prod_sys.config.src"},
{vm_args_src, "config/vm.args.src"}
]}
]}
]}.Key differences from development:
dev_modeisfalseso files are copied into the releaseinclude_ertsistrueso the Erlang runtime is bundled- We use
prod_sys.config.srcinstead ofdev_sys.config.src
Production configuration#
Let’s look at config/prod_sys.config.src:
[
{kernel, [
{logger_level, info},
{logger, [
{handler, default, logger_std_h,
#{config => #{file => "log/erlang.log"},
formatter => {flatlog, #{
map_depth => 3,
term_depth => 50,
colored => false,
template => ["[", level, "] ", msg, "\n"]
}}}}
]}
]},
{nova, [
{use_stacktrace, false},
{environment, prod},
{cowboy_configuration, #{
port => 8080
}},
{dev_mode, false},
{bootstrap_application, my_first_nova},
{plugins, [
{pre_request, nova_request_plugin, #{
decode_json_body => true,
read_urlencoded_body => true
}}
]}
]},
{my_first_nova, [
{db, #{
host => "${DB_HOST}",
port => 5432,
database => "${DB_NAME}",
username => "${DB_USER}",
password => "${DB_PASSWORD}"
}}
]}
].A few things to note:
- Logger level is
infoinstead ofdebug - Logs go to a file instead of stdout
use_stacktraceisfalseso we don’t leak stack traces to usersdev_modeisfalse- We use environment variables for database credentials with the
${VAR}syntax. Rebar3 will substitute these when building the release.
VM arguments#
The config/vm.args.src file controls the Erlang VM settings:
-sname 'my_first_nova'
-setcookie my_first_nova_cookie
+K true
+A30For production you might want to change a few things:
-name my_first_nova@${HOSTNAME}
-setcookie ${RELEASE_COOKIE}
+K true
+A30
+sbwt very_long
+swt very_low-nameinstead of-snamefor full node names (needed for clustering)- Environment variables for the cookie
+sbwtand+swttune the scheduler for lower latency
Building the release#
Build a production release:
$ rebar3 as prod releaseThis creates the release in _build/prod/rel/my_first_nova/. You can start it with:
$ _build/prod/rel/my_first_nova/bin/my_first_nova foregroundOr as a daemon:
$ _build/prod/rel/my_first_nova/bin/my_first_nova daemonOther useful commands:
# Check if the node is running
$ _build/prod/rel/my_first_nova/bin/my_first_nova ping
# Attach a remote shell to the running node
$ _build/prod/rel/my_first_nova/bin/my_first_nova remote_console
# Stop the node
$ _build/prod/rel/my_first_nova/bin/my_first_nova stopBuilding a tarball#
For deployment to another machine, you can build a compressed archive:
$ rebar3 as prod tarThis creates _build/prod/rel/my_first_nova/my_first_nova-0.1.0.tar.gz. You can copy this to your server, extract it and run it. Since we set include_erts to true, the server does not need Erlang installed.
# On the server
$ mkdir -p /opt/my_first_nova
$ tar -xzf my_first_nova-0.1.0.tar.gz -C /opt/my_first_nova
$ /opt/my_first_nova/bin/my_first_nova daemonSSL/TLS#
To enable HTTPS, configure SSL in your Nova settings:
{nova, [
{cowboy_configuration, #{
use_ssl => true,
ssl_port => 8443,
ssl_options => #{
certfile => "/etc/letsencrypt/live/myapp.com/fullchain.pem",
keyfile => "/etc/letsencrypt/live/myapp.com/privkey.pem"
}
}}
]}Alternatively, you can put a reverse proxy like Nginx in front of your Nova application and let it handle SSL termination. This is the more common approach.
Systemd service#
To run your Nova application as a system service, create a systemd unit file:
[Unit]
Description=My First Nova Application
After=network.target postgresql.service
[Service]
Type=forking
User=nova
Group=nova
WorkingDirectory=/opt/my_first_nova
ExecStart=/opt/my_first_nova/bin/my_first_nova daemon
ExecStop=/opt/my_first_nova/bin/my_first_nova stop
Restart=on-failure
RestartSec=5
Environment=DB_HOST=localhost
Environment=DB_NAME=my_first_nova_prod
Environment=DB_USER=nova
Environment=DB_PASSWORD=secret
Environment=RELEASE_COOKIE=my_secret_cookie
[Install]
WantedBy=multi-user.targetSave this as /etc/systemd/system/my_first_nova.service and enable it:
$ sudo systemctl daemon-reload
$ sudo systemctl enable my_first_nova
$ sudo systemctl start my_first_novaDocker#
You can also containerize your application. A simple Dockerfile:
FROM erlang:26 AS builder
WORKDIR /app
COPY . .
RUN rebar3 as prod tar
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y libssl3 libncurses6 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/my_first_nova/*.tar.gz .
RUN tar -xzf *.tar.gz && rm *.tar.gz
EXPOSE 8080
CMD ["/app/bin/my_first_nova", "foreground"]Build and run:
$ docker build -t my_first_nova .
$ docker run -p 8080:8080 \
-e DB_HOST=host.docker.internal \
-e DB_NAME=my_first_nova_prod \
-e DB_USER=nova \
-e DB_PASSWORD=secret \
my_first_novaSummary#
Deploying a Nova application follows standard OTP release practices:
- Configure a production profile in
rebar.config - Set up production config with proper logging and secrets
- Build a release with
rebar3 as prod releaseor a tarball withrebar3 as prod tar - Deploy using systemd, Docker, or any other process manager
The nice thing about OTP releases is that they are self-contained. Once built, you have everything you need in a single directory or archive.
In the next and final article we will look at Nova’s pub/sub system for real-time features.