Your local hugo build works, but GitHub Actions fails randomly. Classic. The root cause is usually not the workflow syntax. It is environment drift, missing permissions, and unstable dependencies.

This guide gives you a stable Hugo deployment workflow for GitHub Pages: pinned versions, minimum required permissions, and actionable failure signals.

1) Deployment rules first

A reliable pipeline should guarantee:

  • Auto build and publish on every push
  • Fixed toolchain versions
  • Fast failure with readable logs

Create .github/workflows/deploy-hugo.yml:

name: Deploy Hugo

on:
  push:
    branches: ["main"]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: "0.145.0"
      TZ: "Asia/Shanghai"
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: ${{ env.HUGO_VERSION }}
          extended: true

      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v5

      - name: Build
        run: hugo --minify --gc

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

3) Why this setup is robust

Pin Hugo version

Never rely on runner defaults. Theme and template behavior can break across versions.

Explicit Pages permissions

pages: write and id-token: write are both required for deployment.

Concurrency guard

If multiple pushes happen quickly, old runs are canceled to prevent stale deploys.

Recursive submodule checkout

If your theme is a submodule, missing recursive checkout will break builds.

4) Top 5 common failures

Error: Failed to create deployment

Check:

  • Repository Settings → Pages → Source is GitHub Actions
  • Workflow includes pages: write and id-token: write

hugo: command not found

Check:

  • actions-hugo step actually ran
  • hugo-version is valid

module not found or theme render errors

Check:

  • submodules: recursive is enabled
  • Local and CI Hugo versions match

No files were found with the provided path: ./public

Check:

  • Hugo build really outputs to public/
  • Config errors did not stop build early

Deploy succeeds but styles are broken

Check:

  • baseURL matches your Pages domain
  • No hardcoded absolute asset paths

5) Add a quick sanity check

Append this after build:

      - name: Sanity check
        run: |
          test -f public/index.html
          echo "Build output looks good"

Simple, but effective against “green build, empty output” mistakes.

6) Summary

For stable Hugo deployment, focus on four things:

  • Pin versions
  • Set correct permissions
  • Pull complete dependencies
  • Keep logs readable for troubleshooting

Get these right and your CI will be more reliable than most blog pipelines.