Deploying a Static Site with Cron and Git27th Apr 2016
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:
- Build the website.
- Push the build folder to a different git branch.
- 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:
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 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
These methods are much simpler than using Git to deploy your static site. Although they’re easy, they come with three drawbacks:
- 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).
- You have to overwrite the build folder on your server, which can take a lot of time. The only exception to this is rsync.
- 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
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.
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:
Before you run the command, make sure you give permissions to the
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
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
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.
This command brings up a file that you can edit:
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
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
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 :)