Deploying Hugo to Cloudflare Pages with GitHub Actions
Cloudflare Pages is one of the best hosting targets for Hugo sites. It is fast, globally distributed, free for most publishing workloads, and integrates cleanly with GitHub repositories. You can deploy directly through Cloudflare’s built-in Git integration — or through a GitHub Actions workflow for more control over the build process. This guide covers both approaches and when to choose each.
Option 1: Cloudflare’s Direct Git Integration
The simplest path requires no GitHub Actions configuration. Connect your repository directly to Cloudflare Pages:
- Log in to the Cloudflare dashboard → Pages → Create a project
- Connect to Git → Select your GitHub repository
- Configure the build:
- Framework preset: Hugo
- Build command:
hugo - Build output directory:
public
- Set environment variables:
HUGO_VERSION=0.145.0(or your target version)HUGO_ENV=production
Cloudflare automatically builds and deploys on every push to your configured branch. Pull requests get preview deployments at unique URLs — useful for reviewing content or theme changes before merging.
When to use this: For most Hugo publishing sites, the direct integration is all you need. It is zero-configuration from the repository side and handles previews automatically.
Option 2: GitHub Actions Deployment
For cases where you need more control — running additional build steps (Pagefind indexing, image processing scripts, content validation), using a specific Hugo version not available in Cloudflare’s preset, or coordinating deployments across multiple repositories — a GitHub Actions workflow gives you that control.
Create .github/workflows/deploy.yml:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive # Required if theme is a Git submodule
fetch-depth: 0 # Full history for .Lastmod to work correctly
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.145.0'
extended: true # Required for Sass/SCSS support
- name: Build
run: hugo --minify --environment production
env:
HUGO_ENV: production
- name: Index with Pagefind
run: npx pagefind --site public
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: your-project-name
directory: public
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref_name }}
Setting Up Secrets
The workflow needs two Cloudflare credentials stored as GitHub repository secrets:
CLOUDFLARE_API_TOKEN: Create at dash.cloudflare.com → Profile → API Tokens → Create Token. Use the “Edit Cloudflare Workers” template and scope it to your account. More precisely, it needs Cloudflare Pages: Edit permission.
CLOUDFLARE_ACCOUNT_ID: Found in your Cloudflare dashboard URL — dash.cloudflare.com/{account-id}/ — or in the right sidebar of any Zone page.
Add both at GitHub → Repository → Settings → Secrets and variables → Actions.
Handling Hugo Themes as Submodules
If your theme is installed as a Git submodule, the submodules: recursive option in the checkout step handles initialization. If you have not initialized the submodule locally:
git submodule add https://github.com/theme-author/theme-name themes/theme-name
git commit -m "Add theme submodule"
Alternatively, vendor the theme directly by copying it into themes/ and committing the files without a submodule relationship. This avoids submodule management at the cost of losing automatic upstream updates.
Branch Previews
The pages-action automatically creates preview deployments for pull requests using the branch name. Cloudflare Pages deploys each branch to a unique preview URL (branch-name.your-project.pages.dev), and the GitHub Actions integration posts the preview URL as a commit status on the pull request.
This enables a complete editorial preview workflow: create a branch, write a post, open a pull request, and share the preview URL for review — all before merging to the main branch and triggering the production deployment.
Caching for Faster Builds
For sites with large image caches or heavy Hugo Pipes processing, caching the resources/ directory between workflow runs speeds up builds significantly:
- name: Cache Hugo resources
uses: actions/cache@v4
with:
path: resources
key: ${{ runner.os }}-hugo-resources-${{ hashFiles('assets/**') }}
restore-keys: |
${{ runner.os }}-hugo-resources-
- name: Build
run: hugo --minify --environment production
The cache key includes a hash of the assets/ directory. When assets change, the cache is invalidated and rebuilt. When assets are unchanged, Hugo restores the processed resources and only rebuilds changed content.
Environment Variables in Hugo Templates
Environment variables set in the Cloudflare Pages dashboard or the GitHub Actions workflow are accessible in Hugo templates through os.Getenv:
{{ $analyticsID := os.Getenv "ANALYTICS_ID" }}
{{ with $analyticsID }}
<!-- Analytics snippet using the ID -->
{{ end }}
Set non-secret configuration variables in the Cloudflare Pages dashboard under Settings → Environment variables. Use GitHub Actions secrets for values that should not appear in workflow logs.
Monitoring Deployments
Cloudflare Pages provides a deployment log for every build under the Pages project dashboard. Failed deployments surface the build output, making it straightforward to diagnose issues.
For the GitHub Actions path, the workflow run log is available in the repository’s Actions tab. Failed steps surface the relevant error output.
Custom Domains
Point a custom domain at your Cloudflare Pages project under Project → Custom domains → Set up a custom domain. If the domain is already on Cloudflare, the DNS configuration happens automatically. For external domains, add the provided CNAME record at your registrar.
Cloudflare Pages provisions and auto-renews SSL certificates for all custom domains.