Setting Up Hugo with Gitea, GitHub, and Cloudflare Pages
Why Hugo?
Hugo is one of the fastest static site generators available, making it ideal for self-hosted and low-maintenance deployments. I wanted a simple, efficient way to publish content without dealing with databases or complex CMS setups. The goal was to maintain full control over the blog while using a streamlined deployment process.
The Setup
I started with a local Hugo installation, managed through an internal Gitea instance for private version control. The blog needed to be public, though, so I mirrored the repository to GitHub, allowing Cloudflare Pages to handle automatic deployments. This approach keeps the primary repo internal while using GitHub as an intermediary for seamless publishing.
1. Installing Hugo
I installed Hugo directly on my server:
apt install hugo
Then, I created the site structure:
hugo new site myblog
Using a Theme: Submodule Issues and Fixes
Hugo themes are typically managed as Git submodules, which caused some headaches when syncing between Gitea and GitHub.
The Submodule Problem
Initially, I added a theme as a submodule:
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/stack
This worked locally, but Cloudflare Pages failed to build, throwing errors like:
fatal: No url found for submodule path 'themes/stack' in .gitmodules
This happened because GitHub wasn’t properly recognizing the submodule reference. Attempts to remove and re-add it using git rm --cached
didn’t fully resolve the issue.
The Proper Fix
The cleanest way to handle submodules for a public build was:
# Remove any broken references
git submodule deinit -f themes/stack
git rm --cached themes/stack
rm -rf .git/modules/themes/stack
rm -rf themes/stack
# Re-add as a proper submodule
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/stack
git submodule update --init --recursive
git commit -m "Re-added Stack theme as a proper submodule"
git push origin main
Once this was done, Cloudflare Pages could properly clone the theme during its build process.
Configuring Cloudflare Pages
With GitHub mirroring the repository, Cloudflare Pages handled automatic deployments.
- Connected the GitHub repo to Cloudflare Pages.
- Set the build command to:
git submodule update --init --recursive && hugo --gc --minify
- Fixed Hugo Version Issues – Cloudflare was running an outdated Hugo version by default, causing errors like:
The fix was to specify the correct Hugo version incan't evaluate field Lastmod in type page.Site
package.json
:This ensured Cloudflare Pages used a compatible version.{ "engines": { "hugo": "0.118.2" } }
Adding a Custom Domain
The final step was setting up a custom domain (blog.spanswick.dev
).
- Added the domain in Cloudflare Pages under
Custom Domains
. - Updated DNS settings:
- Added a CNAME record pointing
blog.spanswick.dev
topages.dev
. - Enabled proxied mode for better performance and security.
- Added a CNAME record pointing
- Enabled Full (Strict) SSL to ensure HTTPS worked correctly.
Final Thoughts
The final setup works smoothly:
- Gitea for internal version control.
- GitHub as a public mirror.
- Cloudflare Pages for automatic deployments.
- Custom domain for a professional look.
The biggest pain point was dealing with submodules—GitHub didn’t always handle them correctly, and Cloudflare’s default Hugo version was outdated. Once those were sorted, everything fell into place.
Going forward, I might explore additional integrations like webhooks to automatically push from Gitea to GitHub, removing the need for manual mirroring.