Using Git to manage a web site

The HTML source for my (i.e., this) web site lives in a Git repository on my local workstation. This page describes how I set things up so that I can make changes live by running just "git push web".

The one-line summary: push into a remote repository that has a detached work tree, and a post-receive hook that runs "git checkout -f".

The local repository

It doesn't really matter how the local repository is set up, but for the sake of argument, let's suppose you're starting one from scratch.

$ mkdir website && cd website
$ git init
Initialized empty Git repository in /home/ams/website/.git/
$ echo 'Hello, world!' > index.html
$ git add index.html
$ git commit -q -m "The humble beginnings of my web site."

Anyway, however you got there, you have a repository whose contents you want to turn into a web site.

The remote repository

I assume that the web site will live on a server to which you have ssh access, and that things are set up so that you can ssh to it without having to type a password (i.e., that your public key is in ~/.ssh/authorized_keys and you are running ssh-agent locally).

On the server, we create a new repository to mirror the local one.

$ mkdir website.git && cd website.git
$ git init --bare
Initialized empty Git repository in /home/ams/website.git/

Although this mirror starts out "bare" (i.e., with no work tree of its own), we add a detached work tree that corresponds to the web server's DocumentRoot (this directory must exist; Git will not create it for you.)

$ mkdir /var/www/www.example.org
$ git config core.worktree /var/www/www.example.org
$ git config core.bare false
$ git config receive.denycurrentbranch ignore

Then we define (and enable) a post-receive hook.

$ cat > hooks/post-receive
#!/bin/sh
git checkout -f
$ chmod +x hooks/post-receive

Back on the workstation, we define a name for the remote mirror, and then mirror to it, creating a new "master" branch there.

$ git remote add web ssh://server.example.org/home/ams/website.git
$ git push web +master:refs/heads/master

On the server, /var/www/www.example.org should now contain a copy of your files, independent of any .git metadata.

The update process

Nothing could be simpler. In the local repository, just run

$ git push web

This will transfer any new commits to the remote repository, where the post-receive hook will immediately update the DocumentRoot for you.

(This is more convenient than defining your workstation as a remote on the server, and running "git pull" by hand or from a cron job, and it doesn't require your workstation to be accessible by ssh.)

Notes

A few more things bear mentioning.

First, the work tree (/var/www/www.example.org above) does not need to correspond exactly to your DocumentRoot. Your repository may represent only a subdirectory of it, or even contain it as a subdirectory.

In the work tree, you will need to set the environment variable GIT_DIR to the path to website.git before you can run any git commands (e.g. "git status").

Setting receive.denycurrentbranch to "ignore" on the server eliminates a warning issued by recent versions of git when you push an update to a checked-out branch on the server. (Thanks to Miklos Vajna for pointing this out.)

Second, you can push to more than one remote repository by adding more URLs under the [remote "web"] section in your .git/config.

[remote "web"]
    url = ssh://server.example.org/home/ams/website.git
    url = ssh://other.example.org/home/foo/website.git

There are also other hooks. See githooks(5) for details. For example, you could use pre-receive to accept or deny a push based on the results of an HTML validator. Or you could do more work in the post-receive hook (such as send email to co-maintainers; see contrib/hooks/post-receive-email).

I wrote this after reading Daniel Miessler's piece on Using Git to maintain your website. His setup is quite straightforward: push to a bare repository on the server, then pull the changes into a second clone that is used as the DocumentRoot. (He later set up a post-update hook on the bare repository to pull into the live clone). Same effect, different implementation.

Note: some people have reported that this strategy doesn't work under git 1.5.4.3 (because the git checkout -f fails). I know it does work with 1.6.x. I haven't investigated further.

Questions and suggestions are welcome.

Abhijit Menon-Sen <ams@toroid.org>
http://toroid.org/ams/
2009-02-26