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…

If you try to access the proxied url and get a 500 Internal Server Error together with [warn] proxy: No protocol handler was valid for the URL /v1. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule. in the logs, you forgot to enable the proxy_http submodule (or are using different ProxyPass target protocol).

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

Note that the exec 2>&1 line is a bashism, /bin/sh need not support it.

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.

If your daemon runs fine but fails to log anything, you need to restart the appropriate supervise process. Apparently supervise doesn't check for new /log subdir if it's already running, even if you call svc -d and svc -u.