As part of my contribution to a recent Random Hacks of Kindness event, I needed to put together a simple deployment mechanism for the static part of the site. The idea was to use a simple
git push to trigger a deployment to production. There was a lot of documentation out here on the web about this, but there were many pitfalls along the way that I thought I’d document here to help others attempting to do the same.
So if you want to deploy static files to an EC2 instance using git and have a rollback archive to go back to then here is what you need to do. I’m going to assume that you’ve set up your EC2 instance to use Linux, and have chosen to serve files from
/var/www/html and you will put your git repos into
I’ll further assume that your local machine is running Unix as well and that your private key for your EC2 instance is called
examplekey.pem. If you’re running Windows everything should work the same, but just look a bit different.
Prepare your server rollbacks
You just need some directories set up on your EC2 server - these just happen to be my preferences.
$ cd /var $ mkdir git $ mkdir www $ mkdir /var/www/backups $ mkdir /var/www/html
Prepare your server git repos
Create a bare repo for ExampleSite as follows
$ cd /var/git $ git clone --bare ExampleSite
Prepare the post-receive git hook
The deployment magic happens using git hooks. In this case you need the
post-receive hook, so do the following
$ cd /var/git/ExampleSite/.git/hooks $ sudo vim post-receive
then populate it as follows:
#!/bin/bash echo "Running post-receive" # Target/working directory - use standard nginx targetdir=/var/www/html echo "cd $targetdir" cd $targetdir # Zip up what was there before, put it to an archive for emergency purposes # /var/www/backups/example.YYYYMMMDDHHMMSSz.tgz time_suffix=`date "+%Y-%m-%d-%H-%M-%S"` echo "Backup timestamp $time_suffix" tar czf ../backups/example.$time_suffix.tgz * # Remove the existing data echo "Remove $targetdir ..." rm -fr $targetdir/* # Check out the local copy of the git repo echo "Check out local copy" export GIT_WORK_TREE=/var/git/ExampleSite export GIT_DIR=/var/git/ExampleSite/.git cd $GIT_WORK_TREE git checkout -f # Copy everything from ExampleSite/src downwards to the above directory (so there's no src dir in the target) echo "Copying to $targetdir" cp -r /var/git/ExampleSite/src/* $targetdir # No TTY interactivity so this is not possible (left in to highlight failing) # echo "/etc/init.d/nginx restart" # /etc/init.d/nginx restart
Make sure that the script is executable with the proper ownership for the SSH login.
$ sudo chmod +x post-receive $ sudo chown ec2-user post-receive
and you’re done.
Prepare your local SSH setup
examplekey.pem file for the EC2 instance, put it into
~/.ssh and set restrictive permissions
$ mv examplekey.pem ~/.ssh $ chmod 400 ~/.ssh/example.pem
Ensure the SSH agent is running on your local machine, and add the key
$ exec ssh-agent $ ssh-add ~/.ssh/examplekey.pem
This will ensure that git can find it for authentication later on.
Git may get confused when deciding which key to use, so use this “belt and braces” approach by creating (or updating) an SSH config file
$ vim ~/.ssh/config Host ExampleHost HostName ec2-1-2-3-4.eu-west-1.compute.amazonaws.com User ec2-user IdentityFile ~/.ssh/examplekey.pem IdentitiesOnly yes
Add the remote repo to your local repo
Add a new remote repo called “production” to your local git repo based on the SSH path to the remote repo on the EC2 instance.
$ cd <path to local repo>/ExampleSite/.git $ git remote add production ssh://ec2-1-2-3-4.eu-west-1.compute.amazonaws.com/var/git/ExampleSite
I found it best to manually edit what git has done to be sure that there is no confusion with keys
[remote "production"] url = ExampleHost:/var/git/ExampleSite fetch = +refs/heads/*:refs/remotes/production/*
To get the above to work, you’ll need to have your
examplekey.pem registered with SSH (see earlier) or you’ll get weird authentication errors. If you’re really struggling you can attempt the SSH authentication manually with OpenSSL
$ ssh -v -i ~/.ssh/examplekey.pem email@example.com
This will shine a light on any problems that you’ll otherwise be scratching your head over.
Do the first push
If no-one has ever done this do an initial push of all data in the master branch (assuming that’s where your production site content lives)
$ git push production +master:refs/heads/master
You must have a bare remote repo (see earlier) otherwise git will starting whining at you. If you need to convert a repo back to a bare one then do the following on the server repo, but you’ll lose any files checked out from the remote repo.
$ cd /var/git/ExampleSite $ mv .git .. && rm -rf * $ mv ../.git . $ mv .git/* . $ rmdir .git $ git config --bool core.bare true
The usual workflow
Make your changes locally, stage and commit as usual then use
$ git push production
to deploy your changes to production. You should se the script running and for typical sites it should take a nanblip to complete resulting in minimal downtime for your site.
Check and rollback
To check simply visit your site. If there is a total smeg up then you can SSH onto your EC2 instance, and do the following to roll back
$ cd /var/www/html $ rm -rf * $ tar -xzvf ../backups/example.<your timestamp>.tgz *
And your old site should come right back as you left it.
Once this system was in place, the developers working on the static side of the project found that they were able to push their changes extremely rapidly out to the production site. This lead to very short turnaround times for minor tweaks to the site, and encouraged frequent updates. Very agile.