Deploy websites using Git - the easy way

After hours of playing around with ssh, sudo, user permissions etc. trying to deploy a PHP website from a Github repository, the best solution I could find was to not use Github at all. I am a big fan of Git and Github, but automatically deploying a website from a Github repository was very fiddly and in the end more trouble than it was worth.

Problems using GitHub post-receive hooks

It seems like a simple idea to use the post-receive URL to notify a server when the GitHub repository has been updated and trigger the server to perform a pull request - updating the website on the server effectively.

I tried doing this many ways; at first I used the backtick operator in a PHP script to perform some basic shell commands like:

`cd /path/to/repository; git pull origin master;`;

The problem with this approach is permissions, if your script is being run as the 'nobody' or 'www-data' users, then you end up running into problems when files are owned by another user (say, user 'frank').

To counter this I tried using 'su' to switch user which required editing the /etc/sudoers file using visudo in order for the 'www-data' or 'nobody' users to log in as 'frank' without needing a password. This didn't work for me.

I tried using proc_open and using the input pipe to pass the password to the su command, that didn't work either. After that I tried using ssh, I thought it was a bit ridiculous to try and ssh into the local server as another user - although it worked on my Mac it didn't work on the server. I also tried adding www-data to the necessary groups but that seemed messy to me.

There are people that seem to get this working without a problem, I'm not sure if our VPS server is set up with stringent security settings but it just wasn't this easy for me.

The solution using a bare git repository on your server

Setting up ssh access for ftp users

We have a VPS, with Plesk installed. Most of the time you create a new account, the new account has an ftp user and no shell access, so the first thing to do is set up shell access for that ftp user using something like:

# change /bin/false to /bin/bash for specific user
usermod -s /bin/bash username

Now you should be able to ssh in to the server with that user (say, 'frank').

Creating a bare git repository

Instead of using Github to manage the central bare repository just create one directly on the server. On Plesk the home directory for user 'frank' (connected to the website frank.com) was /var/www/vhosts/frank.com. This is where the folders httpdocs/, conf/, statistics/ etc. reside.

For this exercise I needed a couple of other folders in frank's root directory: .ssh/ and git/. I had to ssh in as root in order to create these with permissions for the frank user to access.

Logging in as 'frank' again, create the bare git repository in the ~/git/frank.com/ folder using something like:

cd ~/git/
mkdir frank.com/
cd frank.com
git --bare init

Now its just a matter of adding this bare repository as a remote on your other working git repositories. Say you have a repository on your local machine and a repository on the server, which are both going to push and pull to the bare repository we just set up.

On your local repository add the new remote for the bare repository on the server:

#you don't need to name it origin
git remote add origin ssh://frank@yourserver.com/~/git/frank.com
#to check that its added
git remote show origin

On your server repository, the one that will be pulling changes from the new bare repository, you can set up the remote like:

#ssh in to the server and go to the working repository
ssh frank@yourserver.com
cd /httpdocs/
git init git remote add origin ../git/frank.com

Now you can try making a change on your local repository, committing it and pushing it origin. Then ssh in to the server, go to the repository in /httpdocs/ and do a git pull origin master to grab the changes and update the website codebase.

Deploy the changes automatically with git hooks

The whole purpose of this exercise was to use git hooks to deploy the changes to the website whenever the central bare repository is updated. Now with the bare repository hosted on the same server as the website, its quite easy to do so.

All you need to do is edit the post-receive hook on the bare repository, make sure the file is executable and thats it. Something like:

ssh frank@yourserver.com
cd /git/frank.com/hooks/
vi post-receive

#Add these commands to the file
echo "********************"
echo "Post receive hook: Updating website"
echo "********************"

#Change to working git repository to pull changes from bare repository
cd /var/www/vhosts/frank.com/httpdocs || exit
unset GIT_DIR
git pull origin master
#End of commands for post-receive hook

chmod +x post-receive

Now when you push changes to origin from your local repository, the website is going to be automatically updated with those changes. Of course this is just a basic example, if you have a staging environment you might want to pull changes from a staging branch to update the staging site or something like that instead. You might want to use git fetch --all; git reset --hard origin/master; or whatever, world is now your oyster.

Tweaks

You will notice that pushing changes to origin probably require a password every time, which is a pain. Easiest way to get around this is to generate an ssh public key on your local machine (google ssh-keygen) and then add your public key to the authorized_keys file on the server. If you don't have an authorized_keys file on the server, just create one:

ssh frank@server.com
cd ~/.ssh
touch authorized_keys

Then add your local public key to authorized_keys on server:

#On your local machine
cat ~/.ssh/id_*.pub | ssh frank@server.com "cat >> ~/.ssh/authorized_keys"

And you should no longer be prompted for a password when pushing your commits or ssh'ing into the server.

If you want to continue using Github, theres no reason not to. You can set up a Github repository and add it as a remote as well (just keep in mind that the name 'origin' has already been used for a remote repository in this example).

Then you can push changes to Github and the team can use Github to view files, commits, etc. Using this model you do end up pushing changes to 2 different remote repositories which seems a bit needless, the other option is to install something like gitorious or gitosis or even the private github solution onto your server.

Useful/Interesting Links:
Install git on plesk
FTP users shell access on Plesk
Adding ssh key to authorized keys
Hosting your own git repository
Nice model for using git
Git workflow for deploying websites
Website deployment script via ftp
SSH Login script
Silverstripe + Capistrano + Git recipe
My git bookmarks
Using proc_open to authenticate users
Good sudoers example