This is a continuation post from Hugo Setup and Deployment in Hugo series.

In the previous post, we covered the basics of using Hugo. Not only the basics, we also learnt behind the scenes of SSG, getting your site published with GitHub Pages, and finally, we wrote a deployment script written in bash which is used to automatically update the blog’s contents.

In this post, we’ll focus more on the deployment side.

Actually, the deployment script we wrote is not an effective way to deploy our site. The script will generate lots of new HTML files on our machine, and instead of uploading only the files we modified, all these files will be uploaded to the Internet each time we wish to update our site. Now if you care about network bandwidth or things like SSD endurance, that’s probably not a good thing.

Luckily, we can tackle that with GitHub Actions.

GitHub Actions basically does the same thing as our deployment script, but, instead of using our own machine to generate and push the HTML files, we’ll let GitHub VM orDocker instances do it for us.

I know that there are a lot of similar tutorials out there, but let me finish what I’ve started!


The only goal of this post is:

  • Automate Site Deployment using GitHub Actions (I’ll try my best to explain about it!)


Prerequisites are still the same, they are:

  • A GitHub account
  • Familiarity with CLI
  • Familiarity with Git.

A Little Clean Up

Let’s start by reviewing the previous site structure, here’s where we left it:

$ tree -a -L 2 -I "\.git"
├── .gitmodules
├── archetypes
│   └──
├── config.yml
├── content
│   └── post
├── data
├── layouts
├── public # <-- All files under this folder are hosted using GitHub Pages,
│   ├── 404.html
│   ├── assets
│   ├── categories
│   ├── en
│   ├── fa
│   ├── fr
│   ├── index.html
│   ├── index.json
│   ├── index.xml
│   ├── page
│   ├── post
│   ├── robots.txt
│   ├── series
│   ├── sitemap.xml
│   └── tags
├── resources
│   └── _gen
├── static
└── themes
    └── PaperMod

20 directories, 10 files

We’ll now ignore the public folder and remove all of its content including the git repository inside it because this will be handled by GitHub Actions later.

$ rm ./public/* -r f
$ rm ./public/.git -rf

If you happen to use the public folder as a submodule, remove the following lines from .git/config and .gitmodules

[submodule "public"]
        url =
        active = true

Clean any cached content from the public folder.

$ git rm --cached ./public -f

Then let’s create another GitHub repository with the format, because we’re going to use this repo as the remote repository of our site.

$ git remote add origin

For the config.yaml, change the baseURL value with:

baseURL: ""

Add the Theme as Submodule

If you happen to not using theme as submodule previously, this time we will.

Run the following command to add the PaperMod theme we cloned previously as a submodule of our site.

$ git submodule add themes/PaperMod

If you don’t use the same theme, simply replace the url and directory parts to match yours.

Add GitHub Actions

In the deployment script, we’ve automated several things: site build, commit, and push our site to GitHub. We can call the execution of this script as a workflow that runs a single job, it is to deploy our site. Each command executed within the deployment script (build, commit, push) can be referred as a step.

GitHub Actions are basically the advanced version of the deployment script we created. It’s event-driven, which means we can set a specific event to trigger the workflow. In this case, we’ll create an event that will trigger the site deployment each time we push a new commit to the main branch.

A workflow file for GitHub Actions must be written in YAML syntax, and it’s placed under a special directory called .github/workflows/[here].yml, so let’s create the folder first inside our site repository.

$ mkdir -p ./.github/workflows

We’ll save the following codes under the workflow directory with a name gh-pages.yml (./.github/workflows/gh-pages.yml).

name: github pages

# the event
      - main  # Set a branch to deploy

    runs-on: ubuntu-18.04
      - uses: actions/checkout@v2
          submodules: true  # Fetch Hugo themes (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2 
          hugo-version: 'latest'
          extended: true
      - name: Build 
        run: hugo --minify --cleanDestinationDir

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

You can study the code yourself, because I’m not the one who created it 😅. But, to make sure that the public folder always get cleaned first before build, I added the --cleanDestinationDir option.

Let’s commit this workflow and push it to GitHub.

$ git add ./.github/workflows/gh-pages.yml
$ git commit -m "Add gh-pages.yml"
$ git push -u origin main

Trigger the Workflow!

We can create a new post, then add some text to it.

$ hugo new post/
$ echo 'Test GH Action' >> content/post/

Finally, commit the change and push it to your repository to trigger the workflow

$ git commit -m "Trigger build"
$ git push -u origin main

To observe the deployment process/workflow, we can navigate to the site repository and click on the Actions menu (

That’s it!

My Current Deployment

A bit off topic, but I’ll show you my current deployment process.

My deployment process is a little bit different. This is because I’ve separated my blog into two: blog-core (raw markdown) and the full static HTML site (this site). The blog-core is a private repo whereas the full static HTML is a public repository. The blog-core repo contains my draft, notes, and other (a lot of them), which is only available for me locally because of .gitignore.

When a new commit is pushed to the blog-core, GitHub Actions will be triggered to generate the static HTML files. The newly generated HTML files are then pushed to the public repo. I had to include my secret token in the blog-core so that GitHub Actions could use the token to authenticate itself to do some modification on the public repo.


Note: these are not the actual commands.

I’ll separate this section in a dedicated post later! nevermind lol

Well, it’s not in detail, but it should give you an idea of how it works. See you in another post.

I found the blog post that explains about this: Deploy a Public Github Pages site from a Private Hugo Repository. And here’s the Github Actions that I use Hugo Remote.