Deploy your website changes using Git

This is something I’ve been meaning to look at ever since a conversation I had at Scotch on the Rocks 2011. Many of us – even if we use the latest and greatest in source control – still use FTP to deploy our site changes to production. This causes several headaches, among them remembering which files have changed, and getting all those files deployed simultaneously.

After looking around the web, I have found several resources that helped me set up Git to deploy my site.

Git branching model

The first thing to read, if you’ve not already done so, is A successful Git branching model. You can make this as complex as you like, but at the very least you should have a master branch and a develop branch. All your development work is done in the develop branch (or others); when you’ve finished developing a feature and are ready to release it, merge it into the master branch.

The master branch should always contain the production code – so, when we merge our code into the master branch, we then want a way to automate pushing this directly to our production server.

Set up SSH access and Git on your server

The next thing to do is set up your server to allow SSH access (if it doesn’t have it already) and install Git.

I’m running a Windows 2008 server, so decided to use Cygwin to simplify installation of both. I found excellent instructions on setting up a Git server on Windows 2008 – follow all the instructions apart from installing Python, which isn’t needed for out purposes.

Set up remote Git repository

Now that you’ve got Git and SSH installed, here comes the fun stuff.

First, we need to set up a bare Git repository on the server. This should be located somewhere completely separate from your web root. Personally, I put the Git repositories in my cygwin/home/git directory (the home directory for my “git” user).

So, having logged in via SSH as “git”:

$ mkdir mywebsite.git
$ cd mywebsite.git
$ git init --bare

This creates a “bare” Git repo, which means that it contains all the Git commit data, but no checked-out HEAD – essentially, it’s just the contents of the .git directory in a normal git repo.

Now comes the real magic. We’re going to create a Git “hook”, so that when we push the latest commits to this repository, it automatically checks it out to your webroot, with no user intervention necessary!

Create a file post-receive in the hooks directory of your repository:

#!/bin/sh
GIT_WORK_TREE=/path/to/webroot/of/mywebsite git checkout -f

Make sure the target directory exists, as Git won’t create it for you. Finally, set permissions on the file:

$ chmod +x hooks/post-receive

And that’s your server setup completed! (Thanks to this site for the help…)

Push your website

Back on your local machine, set up a remote Git repository – I call mine “production” – which points to the repo on your server:

$ git remote add production git@myserver.com:mywebsite.git
$ git push production +master:refs/heads/master

…and hey presto! Your Git repo has been pushed to the production server – and when it finished the site was automatically checked out into your webroot!

Finally, to update the site in future, simply type:

$ git push production

(Admittedly, I do all my Git committing and pushing through the excellent Tower – which makes it even easier!)

A bit of help needed, please…

I would like a little advice from someone who knows their Git, if possible.

I have files which I don’t want copied to the webserver. Some of these – for instance, user-submitted files – I will add to .gitignore, because I don’t want them subject to source control at all; those are the easy ones.

But there are others – for instance, site config files – which I do want to be under source control, but the version on my live server will be different from that on my dev server, and I don’t want them to be overwritten by the automated update procedure.

Does anyone know of a simple way to achieve this?

 

Comments

John C. Bland II

I considered using hooks but not in this way. Great idea.

The only issue comes into play when there are multiple servers at play. Any ideas on how you could do the same w/ multiple servers?

21 April 2011, 17:31
Rick O

Instead of checking out directly to your web root, check out to a temporary directory first. In that temp directory you can delete any files you don't want to overwrite, fix file permissions, etc. You can then move or rsync the temp content into your web root after it has been fixed up.

John: You can chain pushes. That is, you push to a repo that is set up with a hook that pushes out to all of your machines in turn.

21 April 2011, 18:26
John C. Bland II

So you're saying install the Git server on each machine, push to the main one, and have main push a commit to each of the others? Basically replicating the git repo on each individual server instance.

That's not bad except in an auto-scale scenario. Guess that would involve linking up to the AWS api, pulling the current servers, and using, maybe, rsync to push the updates.

Good stuff though. Thanks!

21 April 2011, 18:38
Seb Duggan

@Rick O: yes, but that does rather defeat the object of having a simplified deployment system!

Thinking aloud: maybe set up a "deploy" branch locally, which automatically strips out files not to be deployed, and then push that to the production server?

21 April 2011, 18:44
Seb Duggan

@Rick O: Of course, I hadn't thought that all your suggestion would be done within the hook script!

Which would make things much more streamlined.

To make things even better, you could keep the script of all the manipulation of the temp directory in the repo itself:

1. Check out to temp dir

2. Run script (now in the temp dir) which manipulates the temp files

3. Move content to webroot

This way, you wouldn't need to go to the server again when you add a new directory/file that doesn't want copying, and it would be under source control too!

21 April 2011, 19:18
Geoff

Your 'deploy' branch would be master. If you're following that "successful branching model" article, you branch from develop to release-1.2.3.4.

You beta test this release for a while - do some fixes etc.

Then comes the production release. You'll want to run some scripts on the release branch which remove any unwanted stuff, then switch to master.

git merge --no-ff release-1.2.3.4

git tag 1.2.3.4

git push production master

So - no cheating! You must go through the release branch / test / tidyup / merge with master push production routine - that way you're always sure which version your live site is running.

21 April 2011, 20:57
Seb Duggan

Thanks Geoff - I thought you might weigh in on this one! I've been waiting for your blog post on the subject, but gave up and tried it for myself!

When you say "run some scripts on the release branch which remove any unwanted stuff", do you mean that there's some stuff that will never be a part of the master branch? So you might have, for instance, server-specific config files which exist in your develop branch, and in your release-1.2.3.4 branch (up until you run the scripts), but never make it into the master branch?

If this is so, it seems weird to me that the master branch wouldn't actually be able to run as a webroot, because there is stuff missing from it... But maybe I've misinterpreted your post?

I must admit that, in a one-person dev team, I tend to run a slightly simplified version of the branching model. But that's just me ;-)

21 April 2011, 21:30
Geoff

Well, to be honest, in our setup we don't have any files to remove.

What we do have is a single settings.ini file with different sections for local, beta and live deployments. We'll then use a script to switch to the correct version.

Also, we host our static content in amazon's cloudfront, but while developing locally, we need to use local resources (css files etc) - so in our develop branch, we'll have:

<link rel="stylesheet" type="text/css" href="<!---http://blah.cloudfront.net--->/media/somefile.css" />

and before merging with master, the comment is removed via a script (ANT in our case - not looked into automating this further) in the release-x.y.z branch.

Our main aim is to be able to push to the live servers without having to worry about whether settings are correct - so master must be kept exactly as we need it for production.

21 April 2011, 22:41
Biesior

In PHP you can use in your config file at the end @include_once('config_local.php'); and then add this *_local file to ignore list, probably in JS or CSS you can do similar thing.

19 May 2011, 11:12
Rodney

If i follow this instructions i get a error. I enter the following command (with my server adress):

git remote add production git@myserver.com" target="_blank">git@myserver.com:mywebsite.git

After this the server returns:

fatal: Not a git repository (or any parent up to mount parent /home)

Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

If i enter git clone git@myserver.com:mywebsite.git the server returns:

Cloning into mywebsite...

warning: You appear to have cloned an empty repository.

Because of that i think, that the connection works. Any ideas ?

01 June 2011, 16:43
Seb Duggan

@Rodney:

On your local machine, make sure you have changed directory in Terminal so you are inside your local git repository when you try the "git remote add..." command.

If you are not, then you will see both the errors you describe.

01 June 2011, 17:01
Rodney

@Seb

Yes you're right. Thanks :)

02 June 2011, 17:24
Jon Brown

Mark Jaquith recent wrote a post about managing local vs server config file relative to WordPress (PHP)

http://markjaquith.wordpress.com/2011/06/24/wordpress-local-dev-tips/

The gist is create a local-config file, then exclude it in .gitignore and in your regular config file include a conditional to check for the file and load it if it exists.

Finally Joe Maller has a great post on a nearly identical method as this, but which includes a post-commit hook on the production server so that if you made changes there they'd propagate back to local. I haven't gotten my hooks working yet though...

19 July 2011, 10:00
Dave Lewicki

A simple way to deal w/ server specific config files

1) exclude config.php w/ .gitignore

2) include each server dependent file in git as

config_dev.php

config_test.php

config_prod.php

3) on each server create config.php as a link to the appropriate file the first time the server is configured.

e.g. on the test server: config.php -> config_test.php

03 August 2011, 09:41
BartWillemsen

Thanks for the instruction! But I have one question.

I have a website which I would like to put under version control. I use a shared hosting plan for that site, so I don't have ssh access to the machine itself, only for my website through a webinterface or FTP.

Is it possible to use git to deploy my local development website to the remote webserver with the same idea as in this article? Or is it only possible with ssh access and git installed?

26 April 2012, 09:28
Seb Duggan

Bart,

I don't think there will be a way - it's not only the ssh, but the remote site also needs to be set up with Git and a Git repository.

I think you'll be stuck with traditional FTPing...

26 April 2012, 09:54
BartWillemsen

But doesn't Git support ftp? Can't I just push the master branch to the remote server through ftp when I updated the master branch through according to that workflow?

26 April 2012, 14:36
Seb Duggan

Perhaps - it's not something I've ever tried...

26 April 2012, 14:44
Jason Wraxall

Just a thought. I no longer have the problem of host specific files because my config is auto-selected from the host name. So I have a php config file with the name of the target host which gets run by my main app script. As i add hosts, I just add another file to the mix and it gets run on the right server...

29 May 2012, 07:27
Nicola

git checkout accepts files/directories, so you can manually specify what to deploy: the rest will be excluded.

You can also use git ls-files if you absolutely want to specify what to exclude from deployment, such as:

<code>

GIT_WORK_TREE=/path/to/webroot/of/mywebsite git checkout -qf -- $(git ls-files | grep -v config)

</code>

This works similar to the _sparse checkout_ feature introduced by git 1.7.0.

What I'm really missing is a way to trigger a command on the remote side before a git pull...

31 May 2012, 15:37
Tristan Olive

If you just want a few config files to retain settings on the server even though you change them locally, use this command on your dev machine:

git update-index --assume-unchanged [files...]

That way the files remain under source control and will be pushed to the web root, but your local changes will be ignored.

(note that if there are multiple people working on the code base, all need to run this command on the protected files, as it is a local setting)

20 August 2012, 21:37
Satish Perala

Thanks for the nice post. My suggestion would be to use capistrano as the deployment tool which allows you to keep your config files in the deploy server different from those in the dev machine.

I would call cap deploy in the git hook and let capistrano handle all the deployment and the versioning.

25 September 2012, 17:55
Dave

Wonderful idea, my only question is how do you use Git to set the appropriate file permissions in this scenario?

18 December 2012, 18:26
Alexandre Peloquin

Awesome! You helped me alot with my setup... saved me great time!

26 December 2012, 22:07
Andrew dixon

I seem to be having some versioning problems. I've used the above setup to update a wordpress site and after I do:

$git push production

I can see my changes on the website. So, pretty good so far. But, when I check the status on the remote, I get the following:

$git status

# On branch master

# Your branch is ahead of 'origin/master' by 1 commit.

#

nothing to commit (working directory clean)

And, when I check the server:

# git status

# On branch master

# Changes not staged for commit:

# (use "git add <file>..." to update what will be committed)

# (use "git checkout -- <file>..." to discard changes in working directory)

#

#   modified: includes/js/stripe-processing.js

#

no changes added to commit (use "git add" and/or "git commit -a")

Do I still need to merge the changes on the server? Just not quite sure what is going on here and I'd appreciate any help.

Thanks!

05 February 2013, 00:46
Lee S

I know this is a very old post, but one trick is to use git archive and have the config files excluded in the .gitarchive file. I would also suggest the deploy use a hash based directory with a symlink pointing to it so you can deploy all the new code, switch the symlink, then bump your web server to pick up the new root.

12 March 2013, 14:16
Luan Nguyen

Thanks.

The git account must have permission to write on /path/to/webroot/of/mywebsite

22 May 2013, 22:33
Mark C

I know this is an old thread, but there appears to be a lot of discussion about how to keep the `.git` files out of sight. This is trivial with Apache. Add the following likes to your VirtualHost block or a .htaccess file and clients will be forbidden from the `.git` directory..

<DirectoryMatch \.git>

Order allow,deny

Deny from all

</DirectoryMatch>

01 June 2013, 15:35
Mark Ma

Git-ftp is a shell script to deploy changed files tracked by git to ftp server on windows.

You can check here: http://redino.net/blog/2013/06/how-to-use-git-ftp-on-windows/

14 June 2013, 12:01