Continuous integration is an integral part of software development today. Git hooks help us accomplish continuous integration, and I can’t imagine how difficult it would have been without git hooks.
If you use git for development then following can help you setup automated deployment of code to your staging and production servers. I used the following to setup staging environment for one of my Django projects, but it can be used with other type of web applications as well.
I wanted to be able to run the app as it will run in production, so I wanted to use Apache with WSGI to host the application. So when I push the code I wanted following to happen in order:
- Update the application code hosted on server.
- Restart apache.
Staging or Production Server Setup
You need to host a bare repository on your server. If you already have that you can skip this section, below I will list down the steps to replicate how my server is setup and how git bare repository is hosted.
- Create a user to host git repository.
useradd -m git
- Set up the password for new user or if you use key based ssh logins set it up for new user and make sure you are able login to remote machine from your development machine.
- Login as git on your server and clone your git repository.
git clone --bare <git repository url>
If you run the above command in home directory of git user you will have a new directory created named “<RepositoryName>.git”. Git will automatically add ‘.git’ at the end of bare repository to differentiate them from the non-bare repositories.
- Goto your development machine and setup new git remote, I am using ‘staging’ in the following command but you can name it what ever you want.
git remote add staging email@example.com:<RepositoryName>.git
- Do a test push to verify everything is working and you can successfully push to your repository on staging.
git push staging +master:refs/heads/master
Setup Git Hooks For Automated Deployment
If you followed the steps above then your bare git repo will be at “/home/git/<RepositoryName>.git”. But for simplicity we will use following variable names.
Bare Repository Path on Remote Server: <RepositoryPath>
Application deployment path (document root): <AppPath>
- Goto your <RepositoryPath>/hooks
- Create a script named: post-receive
Note: It can be a executable script in any language, but I prefer bash for such trivial tasks.
Note 2: Don’t forget to change the variables in the script as per your setup.
#!/bin/bash while read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; then echo "Master ref received. Deploying master branch to production..." git --work-tree=<AppPath> \ --git-dir=<RepositoryPath> checkout -f echo "Restarting Aapche" sudo /usr/sbin/apache2ctl restart else echo "Ref $ref successfully received." echo " Doing nothing: only the master \ branch may be deployed on this server." fi done
- Make script executable.
chmod +x post-receive
- Do a git push and you should see output like. Notice the lines starting with “remote:”. These are the outputs from our script.
Counting objects: 4, done. Delta compression using up to 8 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 375 bytes | 0 bytes/s, done. Total 4 (delta 3), reused 0 (delta 0) remote: Master ref received. Deploying master branch to production... remote: Project update successful... Restarting Aapche To git@<server>.com:<RepositoryName>.git 50244ea..e46a9f0 master -> master
Django Specific Post-receive script
For deployment of Django app there are some extra django specific commands which you might want to run before restarting your apache, so I modified the script that I am using to do following as well before actually restarting apache, and the apache is only restarted if all the commands are successfully completed.
- export Python virtual environment path.
- source virtualenvwrapper.sh
- load the virtual environment
- make sure pip-tools is installed
- run pip sync to make sure all project dependencies are installed and upto date
- run db migrations
- collect static files
- django check to verify everything is good
- restart apache
Below is the script as it is.
#!/bin/bash while read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; then echo "Master ref received. Deploying master branch to production..." git --work-tree=/var/www/MySite \ --git-dir=/home/git/MySite.git checkout -f cat <<PROJECTUPDATE | bash export WORKON_HOME=/path/to/venv source /usr/local/bin/virtualenvwrapper.sh && \ workon MySite && \ pip install pip-tools && \ pip-sync /var/www/MySite/requirements.txt && \ python /var/www/MySite/manage.py migrate && \ python /var/www/MySite/manage.py collectstatic --noinput && \ python /var/www/MySite/manage.py check if [ "$?" -eq "0" ]; then echo "Project update successful... Restarting Aapche" sudo /usr/sbin/apache2ctl restart else echo "Project update failed..." echo "RESOLVE THE ISSUE MANUALLY" fi PROJECTUPDATE else echo "Ref $ref successfully received." echo "Doing nothing: only the master branch may be deployed on this server." fi done