Have you ever desired to host your own personal blog, or a website to showcase your work, for free? All this while taking advantage of a globally distributed CDN to speed up the site’s delivery to your visitors?
Github Pages gives us exactly that!! It give us the ability to host static content (blog/website) for free. We can use that to showcase our work, highlight our achievements, share our resume, setup our personal blog, etc..
We can even use our own custom domain with the site 😎.
All this looks pretty cool. And considering you don’t have to spare a penny on it, makes it even more awesome.
So, what’s the problem here? ¯\_(ツ)_/¯
Well, the problem is if you’re on Github’s free plan, then the repo holding your site’s content needs to be public. It’s all OK if you are creating those static files (js, css, html) manually. But becomes problematic if you’re using any static site generator (like Jekyll, Hugo, MkDocs, etc.).
As much as there’s no harm in hosting your site’s public content on a public repo (js, css, html, images, etc.),
input to your site-geneator is not advisable.
Why? Because it’s part of your development workflow. It might contain unfinished content, environment variables, secret keys, tokens, metadata, etc.. You certainly don’t want random people to be looking at such stuff, right?
This is what this post is all about. Keeping your source files (your site-generator’s input files) hidden, while exposing only the generated files.
All this with one-click change deployments!!!
We’ll learn how to use a private repo hosted on Gitlab, along with it’s CI/CD pipeline to automate the publishing of our site’s content (generated by your favorite static site generator)… all in one single git push !!! How awesome that is 😎.
What all we’ll do
- We’ll be setting up a personal blog website.
- We’ll use Hugo as our static site generator. If you don’t know about it, do read about it a bit. It’s pretty awesome!
- We’ll use one of the many, many freely available Hugo themes, called minimo, to beautify our blog. It’s a simple, but good looking theme.
- Finally, we’ll setup custom domain with the hosted site.
A. Private repo on gitlab to hold our site’s source
- Go to gitlab and create a new account, or use an existing one if you have
- Create a new repo to host your site-generator’s files. Let’s call this repo as site-source
- Now clone this repo locally using
- Follow hugo quickstart to setup base files. Make sure that all files are created in top-level cloned directory, and not a sub-directory
- We now have the base ready to save our site’s source files (these will be read by static site-generator)
B. Public repo on github to hold generated files
- Go to github and create a new account, or use an existing one if you have
- Create a new repo named
usernameis your github account’s username. Note: It’s important to add
.github.ioin this repo name to be able to use this for static site hosting
- Go to repo’s settings page and locate a section named
GitHub Pages. Now choose the branch (
main) and root (keep it
/for now). Also, check the
Enforce HTTPScheckbox. Leave
custom domaininput empty for now. We’ll come back to this later
Preparing for CI/CD
- Go to your
gitlabrepo (site-source), scroll down on left menu and choose
Settings -> CI / CD.
- Now we will add a new
github(it’s github, not gitlab) token here. This will be used by our CI/CD pipeline (in gitlab) to push generated site to our
username.github.iorepo on github.
- To create a new token on github, visit this link: https://github.com/settings/tokens
generate new tokenand choose all scopes under
reposection. Finally, click
generate token. Your new token will be dislayed on screen. Note this down safely. This will not be displayed again.
- Now come back to gitlab, and add a new variable named
GITHUB_ACCESS_TOKENwith value of the generated token. Click
Add Variableto save it.
- At this point our CI/CD pipeline is ready to do work.
Setting-up Gitlab CI/CD configuration file
- Gitlab (or any other CI/CD provider for that matter) makes use of a configuration file which tells the build server
howto build the
Artifactcan be your generated site (as in this case), or compiled code, or anything else that can be deployed to be used by your intended audience.
- Build servers generally use
jsonformats for these files.
- We’ll use following
.gitlab-ci.ymlfile for our purpose:
image: freakynit/hugo-cicd-docker:1.0.4 before_script: - hugo version github_pages: script: - rm -rf public - git clone --depth 1 https://GITHUB_USERNAME:$GITHUB_ACCESS_TOKEN@github.com/GITHUB_USERNAME/GITHUB_USERNAME.github.io.git public - hugo --config config.toml - cd public - git config user.email "GITHUB_EMAIL" - git config --global user.name "GITHUB_USER_FULLNAME" - git add -A - git commit -m "Build from $CI_SERVER_NAME $CI_PIPELINE_ID" - git push artifacts: paths: - public only: - master
- Make sure to replace GITHUB_USERNAME with your github username, GITHUB_EMAIL with your github email, and GITHUB_USER_FULLNAME with your full name.
- Note: DO NOT replace any variable that starts with
$GITHUB_ACCESS_TOKEN). These are replaced by gitlab’s build server while running the pipeline. It makes use of variables we have created earlier in our gitlab repo’s
Variablessection in settings.
CI/CD file explanation
The first and a very important thing to understand is that the build config file is run in the context of the current project. The current working directly, when build is running, is your repo’s root directory, i.e., top-level-directory. Thus, full source code is available to any command which can refer a file on local build box (generally a docker container these days)
- The first line:
tells gitlab’s build server to use
hugo-cicd-docker docker image to run our build pipeline. This docker image already includes the current latest version (
0.80.0) of hugo. It also includes
shell utilities to help us with our build process. You can see the contents of the
Dockerfile used to generate this docker image here freakynit/hugo-cicd-docker:1.0.4
before_script: - hugo version
tells build server to run an array of commands, that should run before each build job. We’ll come to
build jobs just in a while. You can read more about it here: before_script. In this case it simply executes the command
hugo version which just prints hugo’s version on stdout
build job. A build job is what builds artifacts (mostly). It’s a generic name and can be any valid string. The same is displayed on gitlab’s CI/CD pipelines page when a pipeline is running.
github_pages: tells our build server that the following lines are commands that needs to be executed one after the other.
- Build commands:
rm -rf public-> remove existing
publicdirectory, if any. This directory gets created when we run our static site generator, and it holds generated site.
git clone --depth 1 ...-> clone (checkout) our public github repo (
username.github.io) to local filesystem, in
hugo --config config.toml-> run our site-generator with
config.toml(hugo’s own configuration file) to generate our site’s public-facing content. The generated content is by default placed in
publicdirectory. This config can be overwritten in hugo’s config file (
config.toml). See hugo’s documentation on how to customize hugo’s builds
cd public-> switch to hugo-generated site’s root directory (
git config user.email "GITHUB_EMAIL"-> configure git to use this email
git config --global user.name "GITHUB_USER_FULLNAME"-> configure username
git add -A-> add all changed files. Remember?… The public directory actually is a git checkout-out (cloned) repo? It contains content from our previous build (if any previous build was run, or content was manually pushed). When we ran
hugo --config config.tomlcommand in step 3 above, it might have updated some files, added or deleted others. Thus, all these changes needs to be captured before pushing to our public repo on github
git commit -m "Build from $CI_SERVER_NAME $CI_PIPELINE_ID"-> add commit message.
CI_PIPELINE_IDvariables are supplied by default to our build runner by gitlab’s build infrastructure. See gitlab’s documentation for other such variables: predefined environment variables
git push-> push the generated site content to our github’s public repo
artifacts: paths: - public
tells our build server that artifacts (generated site content in our case) are generated in public directory. Build servers generally provde the ability to save generated artifacts for a pre-configured number of days. This is helpful when you want to rollback to a previous build in case your current one fails.
- The last section:
only: - master
tells our build server to
only run the build pipeline when a commit is pushed to
master branch. This allows us to work on multiple features in different branches without triggering unnecessary builds for not-yet-completed features.
Visiting our site
- Go to
https://username.github.comto see your site live. Note that it might take a few minutes to reflect the changes. To verify wheter the latest site content in successfully pushed to our repo or not, go to your github repo’s
commitssection. See when was the last commit pushed. It should be after around when your build pipeline on gitlab ended. If this is not the case, there must be some error. Check pipeline’s build logs.
Adding custom domain
- Purchase a new domain if you don’t have one handly. You can use
namecheap(my personal favorite), or
godaddy(pretty popular alternative). There are hundreds of other providers too. Use google to find your preferred one.
- Go to your domain’s dns settings page. You’ll need to create 4 (1 will also work) type
Arecords, and 1
- Add following targets for
cname, add host
username.github.io. Make sure to replace
usernamewith your github username.
- Come back to your github repo’s
github pagessection and add the same domain name in
custom domainsection. Also, make sure
Enforce HTTPSis checked.
- Now wait for a few minutes to a few hours, or even an entire day. This update can take time. If this is still not working after 24 hours, you’ll need to contact your domain registrar.
A little guide on setting up hugo
Note: It’s better to follow hugo’s official quickstart. This is provided just for easy reference, and may not be the best quiskstart to start with hugo
hugo new site sample-hugo-site-source cd sample-hugo-site-source git init git remote add origin <YOUR GITLAB RPEO'S URL>
2. Minimo Theme
git submodule add https://github.com/MunifTanjim/minimo themes/minimo git submodule init git submodule update cp themes/minimo/exampleSite/config.toml . hugo new posts/my-first-post.md # Now add some content to above page (note that it's in "content/posts" directory) # save the file # start server in hot-reload mode hugo server -D # visit http://localhost:1313/
3. Changes to config.toml
baseURL-> Your site’s url (https://username.github.io OR https://your_custom_domain)
title-> Title of your site
staticDir = ["static"]after
params.infosection -> description shown on your site
params.copyrightsection if needed
- You can play around more such settings. Refer hugo’s official docs for this
.gitlab-ci.yml file as described before
5. Commit and push the changes
Make sure that section Preparing for CI/CD has been completed
That’s all, folks ¯\(ツ)/¯
Drop me a mail at
email@example.com or DM me on twitter if you have any questions or need help on any step.