Deploying node.js with apache & daemontools
So, I’ve started developing a node.js app and I have to deploy it somehow.
There are some deployment tutorials, such as How to Deploy Node JS Applications, With Examples (monit, apache and iptables port redirect) and Deploying Node.js with systemd (systemd, duh) but neither really suits me completely.
The Goals:
- automatic start on boot
- restart on git push
- automatic restart on nodejs crash
- output redirection to syslog
- multiple node.js apps on arbitrary subdomains/paths
- should mix well with non-node.js servers
- no fancy init dependencies, must work with sysvinit but should not depend on it
- should use packages available in vanilla debian
- should not rely on firewall hacks
I don’t care about socket activation and namespace isolation for now but it may become a concern later so an *inetd compatibile solution is preferred.
Also, I prefer to go with solutions I’m already familiar with, if they fit.
What the “multiple node..” and “should mix well..” requirements really mean is that it should be possible to, for example, have api.example.com/v1/ running PHP and api.example.com/v2/ running node.js.
The How:
The problem splits nicely into several distinct tasks, each of which will be handled separately:
url handler - a tool that listens on a set of domains and hands requests to their proper handlers based on domain/path combination - I’ll go with Apache & ProxyPass process management - start on boot and autorestart, traditionally handled by init but more on that later a git hook - simply needs to kill the right process on update, pretty much a non-issue, except for the need to kill the right process and hence a pidfile log redirector - a tool to take process output and log it
Assumptions
For the rest of this post, I’ll assume you have node.js installed in /usr/local, bare git repo in /git/node-app and the app itself in /srv/node-app. I’ll assume your app is the node.js sample app.js, listening on port 8124 and that you want to serve it on api.example.com/v1.
url handler
First off, we’ll set up apache:
apt-get install apache2
a2enmod proxy
a2enmod proxy_http
service apache2 restart
</code>
We need mod-proxy for ProxyPass but don’t enable ProxyRequests unless you need to.
So let’s create a virtualhost:
vim /etc/apache2/sites-available/node-app
<VirtualHost *:80>
ServerName api.example.com
ProxyPass /v1 http://localhost:8124
</VirtualHost>
</code> a2ensite node-app service apache2 reload
As the docs says, balance the slashes, no slash after v1 means no slash after 8124 .. and vice versa. Obviously you can extend the virtualhost with anything else - more ProxyPass directives, ProxyPassMatch to match paths akin to mod_rewrite, a DocumentRoot for static / apache-native contents…
process management
I was really, really tempted to simply go with /etc/inittab here, that’s pretty much all I need, simply adding
n0:23:respawn:/usr/local/bin/node /srv/node-app/app.js
and running telinit q does the job.
However, server management via changing inittab doesn’t feel quite right, the command would be quite a bit longer because of the need to wrap the invocation with a logger and a pidfile manager, it’s incompatible with *inetd and depends on old-init (neither runit, upstart or systemd support inittab).
I’m aware of monit but I’ve never used it, so I’ll go with djb’s daemontools, this also removes the need for a separate pidfile manager (but means the git hook will be daemontools-specific).
debian-specific: apt-get install daemontools daemontools-run The service dir is /etc/service instead of the non-FHS-compliant /service the djb uses.
The setup is pretty much the setup from daemontools faq.
mkdir /etc/service/node-app
vim /etc/service/node-app/run
#!/bin/bash
log stderr as well
exec 2>&1
simply run node js
exec /usr/local/bin/node /srv/node-app/app.js
</code> chmod -R 755 /etc/service/node-app
a git hook
Deployment with git is not really in the scope of this post and you probably already have your own solution anyway. If all you need to do is pull the app dir on git push and restart the server, this will do, assuming proper rights:
vim /git/node-app/hooks/post-receive
#!/bin/sh
pull
unset GIT_DIR cd /srv/node-app/ git pull
restart node
svc -t /etc/service/node-app
</code> chmod +x /git/node-app/hooks/post-receive
If you had used a pidfile-base process management solution, the restart line would look more like
kill `cat /run/node-app.pid`
instead.
log redirector
Another easy part - we want to log from a pipe to syslog, that’s what logger(1) is for. So we amend the service config with:
mkdir /etc/service/node-app/log
vim /etc/service/node-app/log/run
#!/bin/sh
exec /usr/bin/logger -t node-app
</code> chmod -R 755 /etc/service/node-app/log
Aand that’s it really. You may want to setup your syslog to use a separate file and logrotate it, or you can use daemontools’ multilog if you don’t want to use syslog.