How I Push to Production
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 productionand 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:
- Create a new
gituser on the server.$ adduser git $ su git - Add the
~/.ssh/authorized_usersfile in thegithome directory and put your public ssh key in it (you’ll have to do this for anyone that needs ssh access to the repo). - Create a new folder for your repository.
(The convention is to have a
$ cd ~ $ mkdir my-repository.git.gitsuffix in the name here). - Then
cdinto the folder and initialize it.Making the repository$ git init -b main --barebaremeans that the folder only contains what you would normally find in the.gitfolder. In other words, the bare repository only contains the necessary git files and no checkout of said folders and files. That’s also why it makes sense to have the.gitsuffix in the name.
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
doneSomething 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 mainAfter 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!