Deploying Hugo to GitHub Pages with Actions
GitHub Pages is a natural fit for Hugo sites — both are built around static files, and GitHub Actions makes the build-and-deploy loop nearly effortless. Here’s how to wire it all together.
Prerequisites
- A Hugo site in a GitHub repository
- GitHub Pages enabled for the repo (Settings → Pages)
- Pages source set to GitHub Actions (not a branch)
The Workflow
Create .github/workflows/deploy.yml:
name: Deploy Hugo site to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.124.0
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb \
https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build with Hugo
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo \
--gc \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Breaking It Down
Trigger: The workflow runs on every push to main and can also be triggered manually via the Actions tab (workflow_dispatch).
Permissions: The pages: write and id-token: write permissions are required for the deploy step to work with the newer OIDC-based GitHub Pages deployment.
Concurrency: The concurrency group ensures that if a second push comes in while a deploy is running, the in-flight deploy is cancelled and the new one takes over. This prevents stale deploys from overwriting newer ones.
Hugo version: Pin a specific Hugo version rather than using latest. Unpinned versions can cause your build to silently break when Hugo ships breaking changes.
submodules: recursive: Required if your theme is installed as a Git submodule (common with themes from Hugo’s theme gallery).
fetch-depth: 0: Fetches the full git history, which Hugo uses to populate .GitInfo and .Lastmod on pages.
baseURL: The configure-pages action outputs the correct base URL for your repo. Passing it at build time ensures internal links work correctly whether you’re on a custom domain or the default username.github.io/repo-name/ path.
Enabling Pages in the Repo
Go to Settings → Pages in your GitHub repo:
- Under “Build and deployment”, select GitHub Actions as the source
- Save
The first time the workflow runs, it creates the github-pages environment and sets the deployment URL.
Custom Domains
If you have a custom domain, add a CNAME file to your static/ directory containing your domain:
example.com
Hugo copies it to public/ as-is, and GitHub picks it up automatically.
Troubleshooting
Build succeeds but styles are broken: Usually a baseURL mismatch. Check that the URL passed to Hugo matches where the site is actually served.
404 on all pages: Make sure Pages source is set to “GitHub Actions” not a specific branch.
Submodule errors: If you use a theme as a submodule, ensure submodules: recursive is set in the checkout step.