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.
On your server, create a folder for the repo and initialize it:
1 2 3 |
|
and create the directory we’ll check out to:
1
|
|
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Step 3: Push
Finally, in our local repo we’ll add the remote repo as a push target:
1
|
|
and push to it:
1
|
|
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
|
|
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!