Deploying a Static Site with Cron and Git

Published:

You might have noticed that I redesigned my blog recently if you followed me for a while. In this redesign, I switched from Wordpress to a static generator that I’ve created.

I love static site generators. They make it easy for me to create websites without having to go through a CMS like Wordpress. Since my blog is on a static site generator, I managed to simplify my blogging workflow because I don’t need access to the Wordpress backend anymore.

The only major problem I had with static site is that that I’m unable to schedule my articles and publish them on a different date. I tried several methods, burned myself, and finally found a solution that I’m happy to share with you.

There are three steps in my solution:

  1. Build the website.
  2. Push the build folder to a different git branch.
  3. Pull the updates with cron.

Let’s go through them one by one.

Before we move on, I’m going to assume you know what static site generators are. If you don’t check out some of these popular ones:

  1. Jekyll
  2. Middleman
  3. Wintersmith
  4. Metalsmith

Step 1: Build the website

Static site generators (I’m going to call them SSG for short from this point on) usually contain two folders in the project root – The source folder and the build folder.

The source folder(s) (sometimes there’s more than one) is where you write your code. It contains your markdown files, unminified CSS, unminified JavaScript, templates etc.

Each SSG comes with its command to generate a build folder that files that are ready to be served to the public. This build folder contains your posts and pages in HTML format. It also contains minified CSS and JavaScript files.

The command to generate the builder folder for Jekyll is jekyll build. The one for the SSG I’ve created is gulp --prod (because I build this thing on Gulp).

Once your build folder is ready, move on to the next step.

Step 2: Push the Build folder into a Different Git Branch

There are multiple ways to deploy a static site. The easiest way is to transfer files onto your server with commands like sftp, scp and rsync.

These methods are much simpler than using Git to deploy your static site. Although they’re easy, they come with three drawbacks:

  1. You need to start the deployment process manually, which means you can’t update the website if you’re not at your computer (or if you don’t have internet access).
  2. You have to overwrite the build folder on your server, which can take a lot of time. The only exception to this is rsync.
  3. You can’t roll back to the previous build if shit happens. You have to overwrite the build folder on your server again.

Because of these three reasons, I highly recommend you use Git to deploy your site onto your server. Unfortunately, using Git to deploy a static site is such a complicated process that many people never speak of.

Let’s go into more details.

When we use Git, we want to ignore files that are generated by a command to make sure the git commit history remains clean.

What this means is we have to ignore the generated build folder in our git repository. It also means we have to commit the generated build folder into a separate production branch.

So, we have an interesting dilemma here. We need to ignore the build folder, but we also need to commit the build folder at the same time (though on a different branch).

This is hard. It took me a lot of tries to get it right. If you searched through the net, you’ll find that there are no good answers to this dilemma.

The first solution I found was to use git subtree push. This solution requires you to commit the generated build folder into the git repo, so it’s a no go since I wanted to keep my commit history clean.

The second solution I found was to use the git-directory-deploy script made by X1011. I didn’t try it. The deploy.sh file within this script was so complex that it scared me off 😂.

I ended up going with a third solution I found. This was a script by Ryan Burnette. Essentially this third solution follows a similar process as the first solution. We first commit the build folder forcefully into the git repo, push it into a separate branch, then revert the git history.

The third solution is extremely hacky. It forces git to do things in a way that’s not supposed to be done. It also reverts the git commit history at the same time, which can be dangerous.

I was happy this method for a while, until shit happened. I lost an article that I spent more than 6 hours on. To make it worse, I told everyone about the article and then realized that it’s gone. 😭

After I recovered from the shit state I was in, a kind gentleman named Torsten offered to help me create the perfect script that I was looking for.

There are two steps to the solution.

First, you need to set up an orphan branch. This command creates a orphan production branch. Feel free to rename production to anything you want.

git branch --orphan production

An orphan branch is a Git branch that has a new git history. It’s git history has nothing to do with other branches. Since we need a place to commit the build folder, an orphan branch is the perfect choice.

Delete everything after you created the orphan branch. There’s no need to keep anything here. We’ll fill this branch up with the correct files in the second step.

Here’s the command you’ll need:

git rm -rf .

With this, we have completed the setup for the orphan branch. Let’s head back to the master branch and begin the deployment process.

git checkout master

Now, we’re ready to deploy. Onward to the second part.

There are 10 steps in this part. I’ll walk you through it manually, then provide you with a one-step deploy script at the end.

First, commit everything in your source folder into your git repo. This makes sure you have everything saved and ready to go.

Second, generate the build folder with your SSG’s build command.

Third, checkout the production branch with git checkout production.

git checkout production

If this is the first time you’re doing the process, you should see the build folder and some other folders that you gitignored in your master branch.

If this is the second time you’re doing the process, you should see all folders you’ve ignored, plus everything you’ve committed so far.

Four, remove everything. We’re performing a fresh update with new content in the build folder.

git rm -rf .

Five, checkout the .gitignore file from the master branch. This will prevent us from accidentally committing folders that we want to ignore (like node_modules).

git checkout master -- .gitignore

Six, copy all files from the build folder into the current directory. This allows us to the site directly into the production branch. When the copy is done, delete the build folder since we don’t need it anymore. The command is:

mv build/* . && rm -rf build

Seven, stage all new files.

git add .

Eight, commit new files

git commit "Deploy new post"

Nine, push updates to all branches

git push --all

Finally, switch back to your master branch.

git checkout master

Phew, that’s the ten steps!

I don’t want to run these ten steps manually every time I deploy my website. It will be an administrative hell. So, I condensed everything in a one-step deploy script:

Note: I added git stash to the start and end of the deploy script just in case you forgot to commit your changes before deploying them. Kudos to Nicolas for this!

#!/usr/bin/env sh
set -e # Prevents script from running if there are any errors.
git stash save # Stashes everything away incase you didn't commit them
gulp --prod # Step 2, insert your build script here
REV=`git rev-parse HEAD` # Gets commit hash as message
git checkout production # Step 3
git rm -rf . # Step 4
git checkout master -- .gitignore # Step 5
mv build/* . && rm -rf build # Step 6
git add . # Step 7
git commit -m "deployed $REV" # Step 8
git push --all # Step 9
git checkout master # Step 10
git stash pop # Applies previously saved stash so you can continue working on changes. Once applied, removes stash

Let’s call this deploy script deploy.sh. Instead of running all the ten steps manually, all I do to deploy my static site is run this one command:

./deploy.sh

Before you run the command, make sure you give permissions to the deploy.sh script.

chmod -R g=-w+rX deploy.sh

Okay, time to pause for some Q&A.

If you’ve read so far, you might notice that this method is exactly the same as what you do if you used Github pages.

Yes, that’s exactly it. The only difference is I’m naming the branch production instead of gh-pages.

Since it’s the same as deploying to github pages, why don’t I use ready-made plugins out there like gulp-gh-pages and the Github Pages gem?

Well, the reason why I don’t use the Gtihub pages gem is because I’m working on a Node environment. I don’t want an additional language dependency in my build process.

I tried to use gulp-gh-pages, but I found that it seems to work only with repositories that are located on Github. I ran into weird errors trying to use Gitlab, so I gave up.

So… I was forced to figure out a way :)

Anyway, once you’re done pushing the build folder into the production branch, you can move on to the final step.

Step 3: Pull Updates with Cron

The only thing you need to do to update your site is to ssh into your server and do a git pull command.

Note: I assume you know how to initialize a git repo and run the git pull command in your server, so I’m not going into it. If you need help, check out this awesome tutorial by codeschool.

git pull origin production

Since we want the server to pull updates by itself, we can’t ssh in and git pull manually. We need to use cron, a time-based job scheduler.

Most servers allow you to run cron jobs without a problem. If you need a new sever, I highly recommend going with Digital Ocean (Use this link to get $10 off).

Cron allows us to execute commands at specific combinations of time. We can run a command every minute, every hour, every week, every month or even on a specific day of the week.

The syntax for Cron is:

* * * * * command-to-be-executed
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)

# Credits to http://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

You’ll only change the * (which means every) to a number if you want to be more specific. So, if you want to execute a command every minute, you can use the following cron:

* * * * * command-to-execute

If you want to execute a command at the 30th minute mark every hour, you can use the following cron:

30 * * * * command-to-execute

If you want to execute a command every Wednesday at midnight, you can use the following cron:

0 0 * * 3 command-to-execute
## 0:00 hours on Wednesday

Note: Cron runs on your server, which means the we’re using the server’s timezone. You can find out the current time on your server with the date command.

date
Date command on Digital Ocean

The way you create a cron job varies between servers. If you use shared hosting like Bluehost or Justhost, you need to access the cpanel and work from there.

If you use Digital Ocean, you can create your cron job by writing crontab -e in the command line.

crontab -e

This command brings up a file that you can edit:

Default Crontab

What you do is to insert your command into the file. For example, I have a cron job that runs at 6am every Wednesday. It looks like this:

0 6 * * 3 command-to-execute

The command to execute here is to cd into your git directory and do a git pull.

0 6 * * 3 cd /path-to-directory && git pull origin production

Note: Always specify absolute paths when using cron

Cron jobs run on a separate shell, so you won’t be able to see the logs when it does git pull. If you want to make sure that the cron is running properly, you can pipe the output from the command into a logfile like this:

0 6 * * 3 cd /path-to-directory && git pull origin production >> /path-to-directory/logfile

That’s it!

Wrapping Up

So, you’ve just read my recommendation on deploy your static files with Git and Cron. In this article, we covered how to push a subfolder into another git branch, and we covered how to use the cron.

I hope this helps you out in the your deployment process. What did you find useful in this article? Let me know in the comments below!

If you’re interested in hacking your workflow like what I’ve did here, consider leaving your email in the box below :)

Learn to use Gulp

Save hours everyday building and deploying your website because you have a good web development system backing you up.

Start reclaiming your free time with 10 free chapters.