There are several deployment/CI tools available on the web. However, I was looking for a quick way to deploy a prototype app on a VPS without going through those processes. For this simple purpose, a git hook seemed like a legit idea. Thus, I went on this adventure of simple automated deployment with a git hook.
There are several articles showing the process in various steps. But, unfortunately those couldn’t full-fill my need straight forward. Also, I did fell into several obstacle in the way, with several confusions as not being a professional in area of server administration. However, finally, I did able to set this up which seems to be working well. So, I decided to share my experience so that if anyone with less experience in server who might prefer/trying similar way as mine, may get less pain with help of these tiny details 🙂
My Expected Set Up For Deployment With GIT:
Here is what I was trying to do:
- Set up private Git server on a Ubuntu 14.04 VPS.
- Using single Git repository instead of multiple for both development and production server.
- Implement the push to deploy mechanism using git post-receive hook.
So, final outcome will be that, I will be able to do my development on local machine and after push to the git server, it will update the web application automatically without any need to ssh to server etc. I will also be able to manage two seperate server for my application, one is development and one is live(branch ‘master’ is live, ‘dev’ is development). Lets drive into the details.
Set up private Git server on ubuntu:
The official documentation about git on ubuntu is pretty much enough to follow. However, I have few opinions though:
- Though as said there, gitolite is optional, which is true. But I will highly recommend to get that installed as well. It will help you control access for a team of developers very easily/efficiently. You will have a separate account on the system(server) for gitolite after set up, which is very much useful to organize your repositories in a meaningful way. So, do it!
- Before initializing gitolite, create your ssh key pair for each user you will be using interacting the user management. Like, you should have it for the user for gitolite setup(for example ‘git’) and the user for your local machine(in my case ‘rana’). The first account is needed to get access to the ‘gitolite-admin’ repository so that you can add your local user key from there. After your local user have the access, you will no longer have to login to your remote server for giving access to others, just add them locally, commit and push via ‘gitoliate-admin’ repository. This is true for creating new repositories as well. gitolite config changes will reflect on your server with automatically created new repositories as well.
After the above set up you should have your repository roo to “/home/git/repositories” path. By default there won’t be any repositories other than the ‘gitolite-admin’ one. Here is how my gitolite-config look like after setting up on a repository:
repo gitolite-admin
RW+ = root
RW+ = rana
repo testing
RW+ = @all
repo demo.codesamplez
RW+ = rana
RW+ = git
R master = developer
- master = developer
As you can see at the bottom, they are just simple rule to allow developers have access to all branches other than ‘master’, which only admin can access.
Single Git Repository For Both Development and Production:
If you are in serious business, you will may like to have two separate repository for your application, to have intermediate staging server and may be restrict accidental deployment to production. However, I don’t have such scenario, so I decided to have single repository for both development/staging and production. I am differentiating them based on the branch being pushed. So, as long as development works are going on ‘dev’ branch and pushed to server, they will be deployed on a password protected development site(such as dev.domain.tld) and after there is stable state, the dev branch is merged by admin and pushed to repository. Any push to master branch are automatically deployed to production.
Implement the push to deploy mechanism using git post-receive hook:
If you already did some researches on this topic, you might already know git have a set of hooks which are triggered upon occurrence of some certain events. For our current purpose, we will only need access to the ‘post-receive’ hook. All hooks are stored in a sub-directory named ‘hooks’. In my case the full path is “/home/git/repositories/{repo-name}/hooks”. By default, there won’t be any hook file named ‘post-receive’ but another file named ‘post-receive.mirrorpush’.
We could take a copy of that if needed to reuse some of that, but this simple purpose, we won’t need that, lets create our own new file and put the following content there:
#!/bin/sh
read oldrev newrev refname
if [ $refname = "refs/heads/dev" ]; then
echo "===== DEPLOYING TO DEV SITE ====="
WORK_PATH="/var/www/demo.codesamplez";
BRANCH = "dev"
fi
if [ $refname = "refs/heads/master" ]; then
echo "===== DEPLOYING TO LIVE SITE ====="
WORK_PATH="/var/www/dev/demo.codesamplez"
BRANCH = "master"
fi
unset GIT_DIR
cd $WORK_PATH
umask 002
git reset --hard
git pull --verbose origin $BRANCH || echo "git-pull: returned error code"
#set file permission if has chance to change
chmod 750 -R $(ls | awk '{if($1 != ".git"){ print $1 }}')
echo "===== DONE ====="
Code language: PHP (php)
As you can see on the above code, based on which branches changes were pushed to, we are deciding which version to upgrade, dev or live. So basically, what we need to do, go to that branch directory, and get the latest changes. As if there were some uncommitted changes, done either manually or by application, there will be bigger chance to get merge conflict, thus we are resetting such changes first. So, its important that, the directories your application might need to write(for uploading files, writing log etc), you make sure to have those listed in .gitignore file.
If you have some other tasks to do after source update, like update dependency, running tests etc, you might better use some kind of simple build process with makefile or similar mechanism and trigger a call to that command inside this git hook.
Permission concerns:
Also, I did get another permission puzzle here. I was running nginx web server. The source code get updated by the git user ‘git’ and the application is running under the web server user(‘www-data’). Also, after updating the source code, permission of the changes files were getting weird which wasn’t anymore executable for web server user.
Group Permission: You will also need to make sure that new files etc have proper execution permission for the web server user and put both of these user under same group(say ‘www-data’) while the original owner is the ‘git’ user. To achieve this, you can read through this stack-overflow thread on setting proper group permission.
With such way, you won’t have to worry about the group permission anymore as repository update and web server, owner is same for both.
Run web-server as git user: Another way to easily resolve this complexity is to run your webserver(apache/nginx) as the ‘git’ user itself instead of the default ‘www-data’ user. So, for that you might want to run something like this as ‘git’ user:
git@codesamplez:~$ /etc/init.d/nginx start
git@codesamplez:~$ /etc/init.d/nginx stop
git@codesamplez:~$ /etc/init.d/nginx restart
You can follow this stack-overflow thread on running nginx as non-root user to achieve this.
Also, another alternative achieve this is to edit the nginx.conf file(usually located at “/etc/nginx/nginx.conf”) and edit the username at the beginning which will cause all child process other than the root nginx process as your preferred user. However, in this case you will need to make sure wherever your web server need to access, the user should access to those.
Let web-server user update source: However, if you still interested to keep the default web server user, there is an alternative, which is less confusing way to do this by triggering a url on your app, which might do the update as the web server user and don’t have much permission headache. I will describe it more on a separate tutorial which will be coming soon.
Final Words:
As I already mentioned, this is just a quick dirty way to have an automated deployment with git mechanism. If you have any better nearby idea/suggestion for such simple purpose, I am open to it, sure without help of any third-party tool. Also, if you are having any issue following this tutorial or may not clear about any part, ask me via comments, I will try to explain as much as I can. Keep it touch!
Leave a Reply