After the rise of cloud hosting with their multiple environments and slick UIs it’s easy to forget why we started using them in the first place… Git. What if I told you can put Git on any old server? That you can still host sites yourself and deploy code to production with a simple git push? Yes, you can have (host) your cake (git) and eat (deploy) it too.

1. Create the user

A requirement of this method is that the Git user and the Unix user who owns the web accessible directory are the same account. With this structure in place we can work our magic.

We will connect to this user when we push changes up from our local. Create this user like you would any other user on a Unix machine:

sudo adduser <username>

Add the sudo and ssh groups to this user:

usermod -a -G ssh <username>;
usermod -a -G sudo <username>;

2. Give remote user your public key

SSH to the remote server as the <username> user. In their home directory:

mkdir ~/.ssh && chmod 700 ~/.ssh;
touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys;

On your local machine copy the contents of your public key which is usually found at:

~/.ssh/id_rsa.pub

Back on the server paste your public key into authorized_keys:

nano ~/.ssh/authorized_keys

3. Create a bare Git repo

A bare git repo just means that only the “.git” folder exists in the repo’s folder, not the tracked files. The bare git repo becomes the central “host” for the files. This host will then export the tracked files to our web accessible destination directory.

Create a bare repo in your remote user’s home directory:

mkdir .git;
cd .git;
git init --bare;

Note: Though you’ve created this repo in ~/ it will not try to track the contents of your home directory.

4. Push to the bare repo

Add the bare repo “host” as the remote on your local repo:

git remote add origin <username>@<server>:/home/<username>/.git

In the example above <server> is either the server’s IP address or a domain that points to it.

5. On push export to destination directory

On the remote server add a post-receive hook:

nano ~/.git/hooks/post-receive

Paste:

#!/bin/bash
while read old new ref
do
  if [[ $ref =~ .*/master$ ]];
  then
    echo "Deploying master..."
    git --work-tree=/home/<username> --git-dir=$PWD checkout -f
  else
    echo "$ref received but not deployed."
  fi
done

This script will export Git’s working tree (the tracked files) to /home/<username>. Replace <username> with your destination directory. In my case my repo contains a docroot folder which will be web accessible via Apache, so I have no issue exporting the tracked files directly into my user’s home directory.

Note: As mentioned at the start of the tutorial, the destination directory has to be owned by the Git user in order for Git to have permission to create files and folders. This is why we’re exporting within the user’s home directory.

Save file with Control/Command + O and exit nano with Control/Command + X then make the script executable:

chmod +x ~/.git/hooks/post-receive

Save the file and we’re almost done!

Making a deployment

To test things out simply add a dummy commit to your local repo, then git push. Now when the base repo receives the push it exports the latest tracked files to the /home/<username> destination directory. Visit your website’s URL to see the changes!

File System permissions

We’re only human and you’ve likely uploaded files and folders already that are owned by the wrong users and groups. The post-receive hook will only be able to modify files and folders under the control of <username>. So if you git push and something’s not updating then chances are that file/folder is owned by another user.

Find incorrectly owned directories with:

ls -al

Then change the owner and groups of folders and their children with:

chown -R <user>:<group> <directory-path>

Done!

This is my method. If you have any improvements please let me know.