Falk David

How I Push to Production

· 6 min. read


Disclaimer: I am not a sysadmin and am just having some fun setting up my own website :^) This may very well not be the “recommended” way to do things!

After writing a new blog post, and once I am ready to publish, I don’t want to have to log into the server, run a script to build the site, copy files, etc. etc. Ideally, I just want to commit my changes and not have to do anything else.

I decided that I want to try using git hooks on a self-hosted git server (with a repository of my website) to then automatically run the steps of deploying the site.

With the final setup, here’s how I deploy my site:

# On branch 'main'.
$ git commit
$ git push production

and that’s it! The changes are immediately live :^) So let me show you how I did it!

The Git Server

In my case, my VPS is both the git server as well as the server that hosts the website. You could of course run these on different servers if you wanted to. This is just a simple blog, so I want to keep it simple.

To my surprise (as someone who has never set up a git server) it was extremely easy. I used the guide here as a reference. In short:

And that’s basically it for the server side! Like I said, super easy. You should restrict the login shell of the git user to the git-shell so that users can’t ssh into the server and get an interactive shell.

Now on the client side you just add the server as a remote (and name it production so we can literally git push production :^) ).

What’s super neat is that knowing how the git server is set up, you can now see how this is basically the exact same as an ssh login:

$ git remote add production git@<hostname>:<path-to-repo>

Might have been obvious to others but this really made it click for me.

You can put the repository anywhere on the server (as long as the git user has access to it), but if you put it into something like /server/my-repo.git then your remote address also has to include the leading / to indicate that the repo is not in the default (home) folder of the git user.

Ok, so now we can push to that repo, great!

Setting up the GIT Hooks

Git allows you to run some code for specific events like when a client is pushing changes to a server or when the server is receiving changes.

The only thing you need to do to set up such a program is to add it to the hooks/ directory in your .git directory. In our case, we have a bare repo, so the repo directory is the .git directory.

We want to run some extra code after receiving so for that we’ll have to add a post-receive file to the hooks/ directory. Make sure the file is marked as executable, otherwise git will ignore it.

Here’s how my script roughly looks like:

#!/bin/bash

# Get input from git.
while read oldrev newrev ref
do
  # Check if $ref has a match for main.
  if [[ $ref =~ .*/main$ ]];
  then
    # Client pushed to the main branch. Run deploy script.
    /bin/bash/ <path-to-deploy.sh>
  else
    # Client pushed to some other branch. Do nothing.
  fi
done

Something cool to note is that the client sees the output of the post-receive script! So you can e.g. echo something and see it like:

$ git push 
Enumerating objects: 42, done.
...
remote: 
remote: Push to main received. Deploying files to production...
remote: Done. Go to https://blog.example.org/ to see the changes.
remote:

(You might want to make some commands --quiet so that the client doesn’t get spammed, or sees sensitive information).

And that’s it for the git hook!

The Deploy Script

So now we have a script that we run anytime someone pushes to the main branch. And you can basically do anything you want with it. But here’s how I use it.

I’m using hugo to build my site, so what I want is a checkout of the repo that I can run the hugo command in to build the static site and then copy it to the place that I serve it from.

Getting a checkout of the bare repository is actually super simple (but wasn’t obvious to me because I had never done it before).

When running the git command, you can specify what the git directory (--git-dir) should be and what the working directory (--work-tree) (with the actual files) should be. By default the git-dir is any .git directory that git can find and is assumed to be in the top-level working directory.

So to create a checkout of the bare repository, we can do something like:

git --work-tree="<path-to-checkout-dir>" --git-dir="<path-to-repo> checkout" -f main

After getting the checkout, I can cd into the directory and run the hugo command to generate the site.

And finally, hugo will output a public/ folder in the same directory that I can just copy to the path that the site is served from.

Note that the deploy script is run by the git user using bash. That may or may not affect how you approach things (since the git user likely doesn’t have all the access that you might need). In my case, I gave the git user some access to the folder that my site is hosted on so that it can write files to it.

That’s pretty much it

So this is the setup that I am working with and for now I am happy with it. hugo has been very pleasant to work with. I want to look into generating some interactive content next, we’ll see how that goes.

Self hosting made me learn some cool new stuff and I feel like I gained some more git insight. Hope that some of you found this interesting too!

#blog #website #git #server