days since this article was written, please be aware of its timeliness
Preface
Recently, I explored using GitHub Pages in conjunction with GitHub Actions and found it highly suitable for my personal blog scenario, hence this article.
Previously, my blog directly stored the source code in the repository, using GitHub Pages’ default Jekyll setup with a custom domain. However, this approach had several issues:
- Inability to hide source code. The articles under your
_postdirectory could be freely copied and repurposed elsewhere as someone else’s content. - Inability to hide revision history. If you suddenly wanted to remove certain content from your blog and deleted parts of it, others could still access the file’s history through the repository’s commit records, leaving your modifications fully exposed.
- Inability to use Gitalk. The first two issues could be resolved by upgrading to GitHub Pro or higher, as these paid accounts allow private repositories to use GitHub Pages. However, this would prevent the use of tools like Gitalk, which require third-party write access to issues for comments.
- Inconsistencies or untraceable errors between local and production builds. Since GitHub Pages’ Jekyll is a black box for users, troubleshooting becomes impossible.
- Inability to use certain custom/third-party plugins. GitHub Pages only supports [a limited set of plugins]](https://pages.github.com/versions/), meaning tools like jekyll-paginate v2—which enables pagination for categorized posts beyond just the homepage—cannot be used.
For the reasons mentioned above, I decided to use the compiled source code as the content for Github Pages instead of the original source code. Since the blog repository already had some Gitalk-generated issue comments, I created a new repository to store the source code privately, while keeping the old repository as the one for Github Pages deployment—only now publishing the compiled source code. This approach preserves the original comment data while avoiding the aforementioned issues. Since I use force-push every time I compile and publish to Github Pages, file modification history cannot be viewed, providing some privacy protection and increasing the cost of replication. Below is an introduction to this process.
Overall Workflow
The basic workflow is as follows: assume the old repository is called A, and the new private repository for storing the source code is called B. When a new push occurs, it triggers Github Actions in repository B. These Actions then push the compiled source code (i.e., the contents of the _site folder) to repository A. Since repository A is already set up as Github Pages with a custom domain, no additional configuration is needed.
Detailed Process
In Github Actions, several key concepts need to be clarified:
Hierarchy
From largest to smallest:
- The script itself: This refers to the
ci.ymlfile that will be executed by the actions. - Jobs: Configured tasks under
jobs. By default, jobs run in parallel, but dependencies can be set using theneedskeyword. - Steps: Defined under
steps, these are executed sequentially within a job. Each step runs in its own environment context, and a job can have an unlimited number of steps. - Actions: Not all steps run actions, but actions are executed within steps. Actions are specific commands, such as printing the current directory or installing dependencies.
Using Others’ Steps
Actions allow you to use pre-written steps created by others, eliminating the need to write them yourself. For example, if you need to checkout a branch, simply use:
1 | |
The with parameter contains related settings, which can be checked in the step’s documentation.
Encrypted Data
You certainly wouldn’t want sensitive data like Personal Token or other private information exposed in your CI files for everyone to see. Therefore, you need an alternative way to use such data—by encrypting it and referencing it by name. Here, it’s important to understand the differences between types of encrypted data:
- GITHUB_TOKEN. If you’re only performing builds or actions on your own repository, you don’t need to do anything in the repository settings. When the action runs, GitHub automatically generates an environment variable called
GITHUB_TOKENin the context. It can be used for authentication purposes, such as pushing code to the current repository. - Personal Token. If your current CI is in Repository A but needs to perform tasks in Repository B, you’ll require a Personal Token generated by Repository B’s administrator, with the necessary permissions assigned.
- Custom Encryption. After obtaining the Personal Token for Repository B in the second step, how do you use it in Repository A? This is where secrets—custom encryption—come into play. In Repository A’s settings, you need to define a custom variable name, such as
B_REPO_TOKEN, and paste the Personal Token you just acquired. You can then reference it usingsecrets.B_REPO_TOKEN.
Detailed Steps
The above explanation is quite thorough. Below, we directly list the files and analyze them one by one. When needed, you can simply copy this content and make minor adjustments:
1 | |
Here, the final step requires some explanation: X_BLOG_SITE represents my custom encrypted data, with the value being the configured Personal Token. Additionally, when pushing to the remote repository, if the CI only operates on the current repository, there’s no need to hardcode the repository name. Instead, you can use environment variables in the following format:
1 | |
This means using github.XXX to reference relevant information. You can check here for more context on parameter descriptions.
Differences from Travis
I’ve used Travis before, and the core concepts are largely similar. The most notable difference is that GitHub Actions allows referencing pre-written actions from others—similar to ‘requiring’ someone else’s package. You don’t need to maintain this package; you can use it directly. This offers more flexibility, requires less configuration, and enhances reusability.
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.