Deployment
Hugo generates a static site in public/ when you run hugo. This folder contains plain HTML, CSS, and JS that can be hosted anywhere. Below are step-by-step guides for three common platforms.
Netlify
Create netlify.toml in your site root:
[build]
command = "hugo --gc --minify"
publish = "public"
[build.environment]
HUGO_VERSION = "0.139.0"
[context.deploy-preview]
command = "hugo --gc --minify --buildDrafts --baseURL $DEPLOY_PRIME_URL"
[context.deploy-preview.environment]
HUGO_VERSION = "0.139.0"Setup
- Push your site to GitHub
- Go to app.netlify.com > Add new site > Import an existing project
- Select your repo. Netlify auto-detects the
netlify.toml - Click Deploy site
The first build takes about 30 seconds. Netlify gives you a URL like https://random-name-12345.netlify.app.
Custom domain
- In Netlify: Site configuration > Domain management > Add a domain
- In your DNS provider, add a CNAME record pointing your domain to
<your-site>.netlify.app - HTTPS is provisioned automatically by Netlify via Let’s Encrypt
Use DNS only (not proxied) if you’re on Cloudflare. Netlify handles its own CDN and certificate.
Auto-deploy
Every push to main triggers a rebuild. Pull requests get deploy previews at unique URLs.
Key settings
| Setting | Why |
|---|---|
HUGO_VERSION = "0.139.0" | Pins the exact Hugo version. Without it, Netlify uses whatever is on their build image |
$DEPLOY_PRIME_URL | Netlify variable for the correct preview URL, preventing baseURL mismatches |
--buildDrafts in deploy-preview | Lets you preview unpublished posts in PR previews |
Updating the theme
git submodule update --remote --merge
git add themes/mono
git commit -m "Update Mono theme" && git pushNote: The Mono theme repo includes its own
netlify.tomlfor building the demo site. If you’re deploying your own site with Mono as a submodule, use the config above instead.
GitHub Pages
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
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: "0.139.0"
extended: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build
env:
HUGO_ENVIRONMENT: production
run: hugo --gc --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
- 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@v4Setup
- Push your site to GitHub
- Go to Settings > Pages > set Source to GitHub Actions
- The workflow runs on every push to
main
Your site will be at https://<username>.github.io/<repo>/ (project site) or https://<username>.github.io/ (user site).
baseURL
Set baseURL in hugo.toml to match your GitHub Pages URL:
# Project site (repo named anything)
baseURL = "https://username.github.io/my-site/"
# User site (repo named username.github.io)
baseURL = "https://username.github.io/"The workflow overrides this dynamically via actions/configure-pages, so even if it doesn’t match, the deploy will work correctly.
Key details
| Setting | Why |
|---|---|
peaceiris/actions-hugo@v3 | Fast Hugo install. extended: true gets Hugo Extended |
actions/configure-pages@v4 | Provides the correct baseURL automatically for both project and user sites |
submodules: recursive | Fetches the Mono theme submodule during checkout |
concurrency block | Prevents overlapping deployments |
Docker / Self-hosting
For VPS, Docker Compose, Dokploy, Fly.io, Railway, or any container platform.
Dockerfile
Add to your site root:
FROM hugomods/hugo:exts-0.139.0 AS builder
WORKDIR /src
COPY . .
RUN hugo --gc --minify
FROM nginx:1.27-alpine
COPY --from=builder /src/public /usr/share/nginx/html
EXPOSE 80.dockerignore
.git
.github
public
resources
.hugo_build.lockBuild and run
docker build -t my-site .
docker run --rm -p 8080:80 my-siteOpen http://localhost:8080 to verify, then deploy the image to your platform of choice.
Custom baseURL
Override the base URL at build time:
ARG BASE_URL=https://example.com/
RUN hugo --gc --minify --baseURL ${BASE_URL}Then build with:
docker build --build-arg BASE_URL=https://mysite.com/ -t my-site .Available Hugo images
The hugomods/hugo images come in variants:
| Tag | Includes |
|---|---|
0.139.0 | Standard Hugo |
exts-0.139.0 | Hugo Extended + Dart Sass + PostCSS + git |
Use the exts variant if your site uses SCSS or PostCSS. Available tags: docker.hugomods.com.
Troubleshooting
Theme not found
- Verify
themes/monoexists:git submodule status - Make sure
themesDiris NOT set inhugo.toml(Hugo defaults tothemes/) - For GitHub Actions:
submodules: recursivemust be in the checkout step
Build fails with SCSS error
- Hugo Extended is required. On Netlify set
HUGO_EXTENDED = "true", on GitHub Actions setextended: true
CSS broken / wrong URLs
- baseURL mismatch. On Netlify use
$DEPLOY_PRIME_URLfor previews. On GitHub Pages theconfigure-pagesaction handles it automatically
Site loads but pages 404
- For GitHub Pages project sites, all URLs include the repo name prefix. The dynamic baseURL in the workflow handles this