Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for any Debian derivative and shouldn’t need much tweaking for other distros.
Example systemd service
[Unit]
Description=My app daemon
[Service]
Type=simple
User=username
Group=groupname
Restart=on-failure
Environment=MIX_ENV=prod "PORT=4000"
Environment=LANG=en_US.UTF-8
WorkingDirectory=/var/apps/myapp
ExecStart=/usr/local/bin/mix phoenix.server
[Install]
WantedBy=multi-user.target
must be put it in /lib/systemd/system/myapp.service, also note the use of absolute paths and extra verbosity for utf8 support. Control it using systemctl, e.g. sudo systemctl status myapp.service
, sudo systemctl restart myapp.service
etc.
After setting up the service must be enabled by running systemctl enable myapp.service
, this must only be done once so that the system creates symlinks to the service file. Without this step everything will work fine but will not be restarted on boot.
For much more feature rich description s. systemd.service
1.9 and up: using releases
Since 1.9 there is no reason not to use releases, the transition from mentioned service is also pretty easy. In my experience simple service type works fine (and since it’s a recommended way unless something special needed I use it).
An extra step required when deploying - to build a release. The simplest possible case would be to do it on the same server, something like MIX_ENV=prod PORT=80 mix release release-name --overwrite
. S. mix release — Mix v1.13.3 for info about release command and release configs.
The resulting systemd service is not much different:
[Unit]
Description=My app daemon
[Service]
Type=simple
User=username
Group=groupname
Restart=on-failure
Environment=MIX_ENV=prod
Environment=LANG=en_US.UTF-8
Environment=PORT=80
WorkingDirectory=/var/apps/myapp
ExecStart=/var/apps/myapp/_build/prod/rel/release-name/bin/live start
ExecStop=/var/apps/myapp/_build/prod/rel/release-name/bin/live stop
[Install]
WantedBy=multi-user.target
Side note: To be able to use port 80 as in the above example without running as root CAP_NET_BIND_SERVICE Linux capability can be used on packaged runtime, here is a command that achieves it (version number may change so after upgrading Erlang this command must be run again with new path): sudo setcap 'cap_net_bind_service=+ep' /var/apps/myapp/_build/prod/rel/release-name/erts-10.4.3/bin/beam.smp
more info about Linux capabilities capabilities(7): overview of capabilities - Linux man page
UPD: Instead of setting capabilities on executable level it is also possible to do that on service level, this way it would not care about runtime version. It is done with following line in the service file: CapabilityBoundingSet=CAP_NET_BIND_SERVICE
. There is a quirk though (as it often the case with systemd): this would not work unless ambient capabilities are set as well, so the whole following part is needed:
# Add capability to be able to bind on port 80,
# doing it here means we don't care about runtime version and location
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
View logs
Default Phoenix behavior (logging to standard output) plays well with systemd, so you can use journalctl to manage logs, few examples:
journalctl -u myapp.service --since today
journalctl -u myapp.service --since 09:00 --until "1 hour ago"
journalctl -u myapp.service --since "2016-11-10 12:00" --until "2016-11-10 13:00"
s. journalctl for more.
Restart on deploy
To allow for automatic restart of the service, e.g. as part of automatic deployment adjust the sudoers file by running sudo visudo
and add something like that username ALL = NOPASSWD: /bin/systemctl restart myapp.service
so that the user with the name username can restart the service without entering root password
“Old school” logs
Write data to a file (or use off the shelf solution like GitHub - onkel-dirtus/logger_file_backend) and manage logs using Linux lorgotate, here is an example config
/var/apps/myapp/log/*.log {
daily
missingok
rotate 18
compress
delaycompress
notifempty
create 660 username groupname
dateext
dateformat -%Y-%m-%d-%s
su username groupname
}
must be put in /etc/logrotate.d/myapp, dry run / debug sudo logrotate -d /etc/logrotate.d/myapp
, force rotation (for testing) sudo logrotate --force /etc/logrotate.d/myapp
. More on logrotate http://www.linuxcommand.org/man_pages/logrotate8.html
Example upstart service
Relevant if using Ubuntu before 16.04 (not sure how it goes between LTS’), this example uses exrm releases:
description "My app daemon"
setuid username
setgid groupname
start on runlevel [2345]
stop on runlevel [016]
expect stop
respawn
env MIX_ENV=prod
export MIX_ENV
env PORT=4000
export PORT
env HOME=/var/apps/myapp
export HOME
pre-start exec /bin/sh /var/apps/myapp/bin/myapp start
post-stop exec /bin/sh /var/apps/myapp/bin/myapp stop
Example deployment script
To run automatically after deployment for rolling update of a phoenix project with no DB (hence no ecto migrate) in staging environment (hence tests):
cd /var/apps/myapp
MIX_ENV=prod mix deps.get
brunch build --production
MIX_ENV=prod mix phoenix.digest
MIX_ENV=prod mix compile
sudo service myapp restart
mix test
can be triggered by a commit into a specific branch or using web UI, if anything goes wrong the whole thing exits with a non zero status and lets you know about the problem. Makes a nice simple alternative to CI.