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.
- 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 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
AssumptionsFor 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.
First off, we'll set up apache:
apt-get install apache2
service apache2 restart
We need mod-proxy for ProxyPass but don't enable ProxyRequests unless you need to.
So let's create a virtualhost:
ProxyPass /v1 http://localhost:8124
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).
I was really, really tempted to simply go with /etc/inittab here, that's pretty much all I need, simply adding
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).
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.
# log stderr as well
# simply run node js
exec /usr/local/bin/node /srv/node-app/app.js
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:
# restart node
svc -t /etc/service/node-app
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`
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:
exec /usr/bin/logger -t node-app
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.