One of the tricks that helps me move super fast on Quail and related projects is a continuous delivery setup that I use to iterate quickly (on the browser-based portions, anyway). And what’s great about this workflow is that once you’ve got it set up then publishing a release fits exactly into your already-existing workflow, and setting it up requires tools you’re already using.
The trick, of course, is to rely on
This first bit is optional, but I like to maintain a
master branch that always reflects what’s actually deployed in production. Pushing commits to this master branch logically means that those commits should be immediately deployed, but programmers are lazy and I realised long ago that if I could simply automate away the devops-y step of “ssh into the host, build a distribution of static files, and update nginx” then I’d never forget to deploy a finished release again.
So. Let’s automate this puppy.
First, you’ll need a git host running on your production server (I like Gitolite because it also relies on a git-push-to-deploy model, but your mileage may vary). In the project you want to auto-deploy with git, add this host as a new remote:
git remote add production git@<your production host>:<repo>.git
In your remote repository (e.g.
<repo>.git/ wherever you’ve set up Gitolite on your host) you’ll find a directory called
hooks. Git hooks are an incredibly powerful tool that I won’t go into the details of, but basically they’re scripts that are triggered by events happening in your repository. Create a file called
post-receive in your
<repo>.git/hooks directory with the following (obviously replacing
<repo> with a more helpful name!):
#!/bin/bash sudo -u integration /home/integration/<repo>.sh
sudoing, we’ll need to add an exception to
/etc/sudoers to allow our
git user to execute this script (and only this script!) as the
integration user. Add the following to the end of
git ALL=(integration) NOPASSWD: /home/integration/<repo>.sh
I like to keep my git hooks super-simple, so the above simply runs another script as a lower-privilege user that we’ll use to actually do the work of deploying our pushed application. Go ahead and create that user (
sudo useradd integration) , then become them (
su integration; cd ~). Clone the repository you just created (
git clone git@localhost:<repo>.git — you may need to set up key authentication for the integration user in Gitolite), then create the script we referenced in your
post-receive hook at
#!/bin/bash REPO=<repo> INTEGRATION=/home/integration PUBLISH_DIR=/srv/www/<deploy directory>/public_html # Publish master branch to <deploy directory> cd $INTEGRATION/$REPO # Fetch the latest production push git checkout master git pull origin master # Compile a distribution yarn install yarn compile # Remove the existing distribution rm -rf $PUBLISH_DIR/* # Copy the compiled static files into a place where nginx can serve them cp -R dist/* $PUBLISH_DIR # Update permissions on the published files chgrp -R nginx $PUBLISH_DIR/* chmod -R 775 $PUBLISH_DIR/*
Obviously your commands to turn your uncompiled code into static assets will differ.
And that’s it! Work happily on your
develop branch, and when it comes time to push code to production just commit on
git push production master. You’ll see the forwarded output from your
<repo>.sh above (in my case, the output of
yarn install and
yarn compile, and — assuming everything compiles properly — your production-ready assets will be pushed out all the way to where your web server can serve ‘em.