@jhickner's blog

At work I run a bunch of small apps that do little analytics and monitoring
tasks, all spread across servers at Rackspace and Amazon. Haskell is great for
these types of apps since the resident memory usage is often only 2-4mb, which
makes good use of low-ram servers.

This post describes a simple deployment setup that lets me run unmodified
haskell programs as daemons and have them
automatically re-compiled and re-launched when I do a git push.

Git hooks and Upstart/systemd make this a snap. At the end of this post we’ll have the standard daemon commands like stop, start and restart, automatic rolling log files and even automatic respawning if our program crashes, all for the price of a tiny config file.

We’ll use a remote monitoring app called intrepid that I’ve been working on as an
example, and I’ll walk through the steps to set up easy deployment.

Step 1: Set up a remote Git repo

Assuming you have a local copy of your app’s source stored in git and complete with a proper .cabal file (easy to create with cabal init), the next step is to set up a receiving repo you can push to. We’ll make it --bare, because it’s actually simpler to do a checkout from a bare repo after every push and then run that than it is to try to use the repo’s working directory.

We’ll push to this repo in a minute, but first let’s do some setup. In the bare repo there’s a hooks directory. You can place scripts here that will be run after or before various actions are performed on the repo (more on hooks here).

We’ll set up a post-receive hook that will run after the repo recieves a push. Our hook will do a checkout, build our app and restart it. Create a file called post-receive and place it in the repo’s hooks directory. Don’t forget to
chod +x so it’s runnable.

All set! This script checks out our code, builds it and restarts after every push. Next we’ll set up Upstart, which will provide that restart line at the
bottom.

Step 2: Setting up Upstart/systemd

Upstart and systemd are two popular replacements
for the venerable SysV init, the program we’ve been using to stop and start
unix daemons for over 150 years (approximately). Depending on your distro,
you probably have one or the other installed already. The setup for each is
very similar.

Here’s an example Upstart config file for intrepid:

intrepid.conf

123456789101112131415

description "Intrepid - remote monitoring tool"# start after the filesystem and ethernet are upstart on (local-filesystems and net-device-up IFACE=eth0)stop on shutdown
# restart automatically if we crashrespawn
# tell upstart how to start our appscript
cd /home/apps/projects/Intrepid
dist/build/intrepid/intrepid
end script

Your conf file should be named <appname>.conf and placed in /etc/init. That’s it! We get a lot for those few lines, but there’s a lot more Upstart can do (docs).

And here’s an example systemd service file (courtesy of vagif on reddit), which should be placed in
/etc/systemd/system.

Step 3: Push

Finally, in our local repo we’ll add the remote repo as a push target:

1

git remote add apps ssh://apps@appserver/~/projects/Intrepid/repo

and push to it:

1

git push apps master

If all goes well, you should see your app compiling successfully, followed by
a message like this from upstart, telling you your app is running:

1

intrepid start/running, process 5037

And we’re done! You now have all the standard daemon commands, so you can
ssh to your server and stop/start/restart/status intrepid if you have Upstart or
systemctl start/stop/restart/status intrepid on systemd. If it crashes, it will be automatically
restarted. Upstart will place log files at /var/log/upstart/<appname>.log.

And of course, every time you git push, your
running app will be automatically updated!