Building and Deploying a Blog with Hugo and GitHub Pages
In today's fast-paced digital age, where information is just a Google or ChatGPT search away, setting yourself apart is more important than ever. Blogging is one of the most effective ways to showcase your knowledge, contribute to the community, and build your brand. Think of it as a dynamic extension of your resume, a portfolio that grows and evolves with you. It provides a platform to share ideas, tackle complex problems, and express your viewpoints while delivering immense value to your readers.
Prerequisites
- Git
- A GitHub account
- GitHub CLI
- PowerShell version 7 or higher (Do not use Windows PowerShell, per the Hugo website)
- Basic knowledge of Markdown
Install Hugo
Hugo is a static site generator built with Go. It's popular for its speed, flexibility, and ease of use. Hugo Extended is a variant of Hugo that supports additional features like Sass/SCSS compilation, image processing, and more. Hugo Extended is Hugo but with some extra capabilities to help manage more complex projects. For more information, see the Hugo website.
Install Hugo Extended using your operating system's package manager. While the following aren't the only ways to install Hugo, they're how I installed it on each operating system I use. For additional options, see the installation section of the Hugo website.
Windows
Install Hugo Extended using the Windows Package Manager.
1winget install Hugo.Hugo.Extended --source winget 1Found Hugo (Extended) [Hugo.Hugo.Extended] Version 0.119.0 2This application is licensed to you by its owner. 3Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. 4Successfully verified installer hash 5Extracting archive... 6Successfully extracted archive 7Starting package install... 8Command line alias added: "hugo" 9Successfully installed macOS
Use the Homebrew package manager to install Hugo Extended.
1brew install hugo 1==> Downloading https://ghcr.io/v2/homebrew/core/hugo/manifests/0.119.0 2==> Fetching hugo 3==> Downloading https://ghcr.io/v2/homebrew/core/hugo/blobs/sha256:6bc80ff59b610e29a2ace40e6f473b3a8e3703c8692374f5fa2ab 4==> Pouring hugo--0.119.0.arm64_sonoma.bottle.tar.gz 5==> Caveats 6zsh completions have been installed to: 7 /opt/homebrew/share/zsh/site-functions 8==> Summary 9🍺 /opt/homebrew/Cellar/hugo/0.119.0: 50 files, 68.3MB 10==> Running `brew cleanup hugo`... 11Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP. 12Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`). Linux
Use the package manager for your distribution to install Hugo Extended. The following example uses the APT package manager to install Hugo Extended on Ubuntu.
1sudo apt install hugo 1Reading package lists... Done 2Building dependency tree... Done 3Reading state information... Done 4The following NEW packages will be installed: 5 hugo 60 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. 7Need to get 11.2 MB of archives. 8After this operation, 48.3 MB of additional disk space will be used. 9Get:1 http://ports.ubuntu.com/ubuntu-ports jammy-updates/universe arm64 hugo arm64 0.92.2-1ubuntu0.1 [11.2 MB] 10Fetched 11.2 MB in 1s (8547 kB/s) 11Selecting previously unselected package hugo. 12(Reading database ... 203526 files and directories currently installed.) 13Preparing to unpack .../hugo_0.92.2-1ubuntu0.1_arm64.deb ... 14Unpacking hugo (0.92.2-1ubuntu0.1) ... 15Setting up hugo (0.92.2-1ubuntu0.1) ... 16Processing triggers for man-db (2.10.2-1) ... Create a Hugo site
Using PowerShell, navigate to the parent directory on your computer's file system where you want to create your Hugo site. The examples in this article use the $HOME/Developer/git directory as the parent path.
1Set-Location -Path $HOME/Developer/git Create a new Hugo site. The examples in this article use myhugosite for the Hugo site name.
1hugo new site myhugosite 1Congratulations! Your new Hugo site was created in /Users/mikefrobbins/Developer/git/myhugosite. 2 3Just a few more steps... 4 51. Change the current directory to /Users/mikefrobbins/Developer/git/myhugosite. 62. Create or install a theme: 7 - Create a new theme with the command "hugo new theme <THEMENAME>" 8 - Install a theme from https://themes.gohugo.io/ 93. Edit hugo.toml, setting the "theme" property to the theme name. 104. Create new content with the command "hugo new content <SECTIONNAME>/<FILENAME>.<FORMAT>". 115. Start the embedded web server with the command "hugo server --buildDrafts". 12 13See documentation at https://gohugo.io/. Navigate to the root directory of your newly created Hugo site.
1Set-Location -Path $HOME/Developer/git/myhugosite Create a Git repository
Initialize the site's directory as a Git repository.
1git init 1Initialized empty Git repository in /Users/mikefrobbins/Developer/git/myhugosite/.git/ Add a .gitignore file
1$gitIgnore = @' 2.DS_Store 3.hugo_build.lock 4/public/ 5/resources/_gen/ 6'@ 7Set-Content -Path $HOME/Developer/git/myhugosite/.gitignore -Value $gitIgnore -Force Add a theme
Adding a theme to a Hugo website can enhance its aesthetics and functionality. Hugo offers various methods for installing and using themes. This article uses Hugo modules to add a theme to the site. See your theme's documentation for guidance on adding a theme to a Hugo site.
Initialize a new Hugo module. This initialization creates a go.mod file in the root of the site. The go.mod file tracks the dependencies of the site and the version of Go that the site uses. Hugo and GitHub Actions use the version of Go when building the site.
1hugo mod init github.com/<github-username>/myhugosite 1go: creating new go.mod: module github.com/mikefrobbins/myhugosite 2go: to add module requirements and sums: 3go mod tidy Remove the Go patch version from the go.mod file. This modification is required because GitHub Actions only supports Go specified in Major.Minor format. GitHub Actions builds and deploys the site to GitHub pages and fails if the go.mod file includes the patch version. For more information about Semantic versioning, see semver.org.
1Get-Content -Path ./go.mod -OutVariable content 2$content -replace '([^\.]*\.[^\.]*)\..*', '$1' | Out-File -FilePath ./go.mod -Force 3Get-Content -Path ./go.mod 1module github.com/mikefrobbins/myhugosite 2go 1.21.3 3 4module github.com/mikefrobbins/myhugosite 5go 1.21 Add a theme to the Hugo site. This article uses the Relearn theme for Hugo.
1hugo mod get github.com/McShelby/hugo-theme-relearn 1go: downloading github.com/McShelby/hugo-theme-relearn v0.0.0-20240304200407-d81b4dd6eb9a 2go: added github.com/McShelby/hugo-theme-relearn v0.0.0-20240304200407-d81b4dd6eb9a Configure Hugo to use the Relearn theme.
1Add-Content -Path hugo.toml -Value "theme = 'github.com/McShelby/hugo-theme-relearn'" Add content
Create a new post. The following example uses the name my-first-article.md for the new post. The name of the post is the name of the file created in the content/posts directory.
1hugo new content posts/my-first-article.md 1Content "/Users/mikefrobbins/Developer/git/myhugosite/content/posts/my-first-article.md" created Add content to the newly created my-first-article.md post. You write the content for Hugo posts in Markdown. Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. Title and date are the only metadata elements specified in the following example. See the documentation for your specific theme to determine the supported metadata.
1$post = @' 2--- 3title: "My First Article" 4date: 2023-10-26T06:30:00-05:00 5--- 6 7Insert lead paragraph here. 8 9> Block quote 10 11**bold** 12 13_italic_ 14 15Unordered list 16 17- first 18- second 19- third 20 21Ordered list 22 231. first 241. second 251. third 26 27Inline code `Get-Help`. 28 29Cmdlet name `Get-Help`. 30 31Parameter name **ParameterName**. 32 33Parameter value `$true`. 34'@ 35Set-Content -Path $HOME/Developer/git/myhugosite/content/posts/my-first-article.md -Value $post Build the site locally
Start the Hugo server locally on your computer. Hugo serves the site locally so that you can view a live preview in your web browser. The Hugo server watches for changes to the site and automatically rebuilds it when changes are detected.
1hugo server 1Watching for changes in /Users/mikefrobbins/Developer/git/myhugosite/{archetypes,content,layouts,static} 2Watching for config changes in /Users/mikefrobbins/Developer/git/myhugosite/config/_default, /Users/mikefrobbins/Developer/git/myhugosite/config/_default/menus, /Users/mikefrobbins/Developer/git/myhugosite/go.mod 3Start building sites … 4hugo v0.119.0-5fed9c591b694f314e5939548e11cc3dcb79a79c+extended darwin/arm64 BuildDate=2024-03-07T13:14:42Z VendorInfo=brew 5 6 | EN 7-------------------+------- 8 Pages | 10 9 Paginator pages | 0 10 Non-page files | 0 11 Static files | 204 12 Processed images | 0 13 Aliases | 0 14 Sitemaps | 1 15 Cleaned | 0 16 17Built in 101 ms 18Environment: "development" 19Serving pages from memory 20Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender 21Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) 22Press Ctrl+C to stop Consider starting hugo server as a background job because otherwise, it ties up your PowerShell prompt or session.
1Start-Job -Name hugo -ScriptBlock {hugo server} -WorkingDirectory $HOME/Developer/git/myhugosite Visit the local site in your default web browser. Port 1313 is the default, but a different port is dynamically assigned if the default port is already in use.
1Start-Process http://localhost:1313/ The website should look similar to the following.
Stop the Hugo server by pressing CTRL+C in the PowerShell console or by stopping the PowerShell job, depending on how you started it.
1Get-Job | Stop-Job -PassThru | Remove-Job Create a repository on GitHub
Add and commit the changes to your local Git repository.
1git add . 2git commit -m 'Initial commit' 1[main (root-commit) 89e1737] Initial commit 2 6 files changed, 50 insertions(+) 3 create mode 100644 .gitignore 4 create mode 100644 archetypes/default.md 5 create mode 100644 content/posts/my-first-article.md 6 create mode 100644 go.mod 7 create mode 100644 go.sum 8 create mode 100644 hugo.toml Tip: To authenticate with GitHub using the GitHub CLI, run
gh auth login.
Create a new repo on GitHub using the GitHub CLI and push your local clone to it. You can only use GitHub Pages with a public repo if you're using a free GitHub account. Private repos with GitHub Pages require a paid GitHub account.
1gh repo create myhugosite --public --push --source=. --description 'My hugo website' 1✓ Created repository mikefrobbins/myhugosite on GitHub 2 https://github.com/mikefrobbins/myhugosite 3✓ Added remote https://github.com/mikefrobbins/myhugosite.git 4Enumerating objects: 11, done. 5Counting objects: 100% (11/11), done. 6Delta compression using up to 12 threads 7Compressing objects: 100% (7/7), done. 8Writing objects: 100% (11/11), 1.31 KiB | 1.31 MiB/s, done. 9Total 11 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) 10To https://github.com/mikefrobbins/myhugosite.git 11 * [new branch] HEAD -> main 12branch 'main' set up to track 'origin/main'. 13✓ Pushed commits to https://github.com/mikefrobbins/myhugosite.git Deploy the site to GitHub Pages
Enable GitHub Pages and set it to deploy via GitHub Actions. Use the GitHub CLI with the GitHub API to update the repo settings.
1gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/<github-username>/myhugosite/pages -F "source:branch=main" -F "source:path=/" -F "build_type=workflow" 1{ 2 "url": "https://api.github.com/repos/mikefrobbins/myhugosite/pages", 3 "status": null, 4 "cname": null, 5 "custom_404": false, 6 "html_url": "https://mikefrobbins.github.io/myhugosite/", 7 "build_type": "workflow", 8 "source": { 9 "branch": "main", 10 "path": "/" 11 }, 12 "public": true, 13 "protected_domain_state": null, 14 "pending_domain_unverified_at": null, 15 "https_enforced": true 16} Add the GitHub Actions workflow folder locally.
1New-Item -Path $HOME/Developer/git/myhugosite/.github/workflows -ItemType Directory -Force 1Directory: /Users/mikefrobbins/Developer/git/myhugosite/.github 2 3UnixMode User Group LastWriteTime Size Name 4-------- ---- ----- ------------- ---- ---- 5drwxr-xr-x mikefrobbins staff 10/25/2023 20:15 64 workflows Save the following GitHub Actions workflow to a file named Hugo.yaml in the workflow folder. Run hugo version on your computer to determine what version of Hugo you're using and update the version in the workflow. The following workflow is a modified version of the one found in the Host on GitHub Pages section of the Hugo website.
1$workflow = @' 2# Sample workflow for building and deploying a Hugo site to GitHub Pages 3name: Deploy Hugo site to Pages 4 5on: 6 # Runs on pushes targeting the default branch 7 push: 8 branches: 9 - main 10 11 # Allows you to run this workflow manually from the Actions tab 12 workflow_dispatch: 13 14# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15permissions: 16 contents: read 17 pages: write 18 id-token: write 19 20# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22concurrency: 23 group: "pages" 24 cancel-in-progress: false 25 26# Default to bash 27defaults: 28 run: 29 shell: bash 30 31jobs: 32 # Build job 33 build: 34 runs-on: ubuntu-latest 35 env: 36 HUGO_VERSION: 0.119.0 37 steps: 38 - name: Install Hugo CLI 39 run: | 40 wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ 41 && sudo dpkg -i ${{ runner.temp }}/hugo.deb 42 - name: Install Dart Sass 43 run: sudo snap install dart-sass 44 - name: Checkout 45 uses: actions/checkout@v3 46 with: 47 submodules: recursive 48 fetch-depth: 0 49 - name: Setup Pages 50 id: pages 51 uses: actions/configure-pages@v3 52 - name: Install Node.js dependencies 53 run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" 54 - name: Build with Hugo 55 env: 56 # For maximum backward compatibility with Hugo modules 57 HUGO_ENVIRONMENT: production 58 HUGO_ENV: production 59 run: | 60 hugo \ 61 --gc \ 62 --minify \ 63 --baseURL "${{ steps.pages.outputs.base_url }}/" 64 - name: Upload artifact 65 uses: actions/upload-pages-artifact@v1 66 with: 67 path: ./public 68 69 # Deployment job 70 deploy: 71 environment: 72 name: github-pages 73 url: ${{ steps.deployment.outputs.page_url }} 74 runs-on: ubuntu-latest 75 needs: build 76 steps: 77 - name: Deploy to GitHub Pages 78 id: deployment 79 uses: actions/deploy-pages@v2 80'@ 81Set-Content -Path $HOME/Developer/git/myhugosite/.github/workflows/hugo.yaml -Value $workflow -Force Add, commit, and push the changes. Pushing the changes to the main branch of your GitHub repo triggers the GitHub Actions workflow. The GitHub Action is a build-and-deploy workflow that builds and deploys the site to GitHub Pages. The GitHub Actions workflow automatically runs when you update the main branch, and you can manually trigger it by running gh workflow run 'Deploy Hugo site to Pages'.
1git add . 2git commit -m 'Added GitHub Action workflow' 3git push 1[main 7afd717] Added GitHub Action workflow 2 1 file changed, 78 insertions(+) 3 create mode 100644 .github/workflows/hugo.yaml 4 5Enumerating objects: 6, done. 6Counting objects: 100% (6/6), done. 7Delta compression using up to 12 threads 8Compressing objects: 100% (3/3), done. 9Writing objects: 100% (5/5), 1.35 KiB | 1.35 MiB/s, done. 10Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0) 11remote: Resolving deltas: 100% (1/1), completed with 1 local object. 12To https://github.com/mikefrobbins/myhugosite.git 13 89e1737..7afd717 main -> main You can see the name of all your GitHub Actions workflows using the GitHub CLI.
1gh workflow list 1NAME STATE ID 2Deploy Hugo site to Pages active 89112170 Check the status of the GitHub Actions workflow. It may take a few minutes for it to complete.
1gh workflow view 'Deploy Hugo site to Pages' 1Deploy Hugo site to Pages - hugo.yaml 2ID: 89112170 3 4Total runs 1 5Recent runs 6 TITLE WORKFLOW BRANCH EVENT ID 7✓ Added GitHub Action workflow Deploy Hugo site to Pages main push 8223964591 8 9To see more runs for this workflow, try: gh run list --workflow hugo.yaml 10To see the YAML for this workflow, try: gh workflow view hugo.yaml --yaml Tip: The workflow failed if you didn't remove the Go patch version from the go.mod file, as described earlier in this article.
Once the GitHub Actions workflow completes successfully, visit the public website in your default web browser.
1Start-Process https://<github-username>.github.io/myhugosite/ The website should look identical to the local one you previewed and similar to the following.
Clean up resources
If the resources created in this article aren't needed, you can delete them by running the following commands.
Warning: The following commands perform non-recoverable destructive changes. If resources outside the scope of this article exist in the specified local directory or GitHub repo, these commands also delete them.
The following commands delete the site, GitHub repo, and local directory used in this article. I've placed a comment symbol before each command so you don't run them accidentally.
Tip: Deletion requires authorization with the delete_repo scope. To authorize, run
gh auth refresh -s delete_repo.
1# Get-Job -Name hugo -ErrorAction SilentlyContinue | Stop-Job -PassThru | Remove-Job 2# Remove-Item -Path $HOME/Developer/git/myhugosite -Recurse -Force 3# gh repo delete myhugosite --yes 1✓ Deleted repository mikefrobbins/myhugosite Summary
This article outlined how to set up a free blog using Hugo and GitHub Pages. It covered essential prerequisites, installation, and content creation. It also provided step-by-step instructions for local testing and online deployment through GitHub.
While outside the scope of this article, I recommend using a custom domain name for your blog in case you decide to move it somewhere else; you can maintain the existing URLs for your content. It's also easier to remember and share with others.