From Push to Production: Hugo Static Website Deployment

Table of Contents

Finding the fastest way to publish static Hugo websites isn’t always straightforward.
After finishing your site, you’ll probably ask yourself: What’s next? and How do I deploy it easily to production?

In this article, I’ll share a simple approach to build, publish, and prepare your site for containerized environments like Docker or Kubernetes—just like the website you’re reading right now.

So, let’s say your site is ready and you already connected a GitHub Repository. From a DevOps perspective, there are many ways to bring it to production. Here, we’ll focus on a clean workflow using GitHub, GitHub Actions, and Docker.

Your Repository should look like this:

Repo

Dockerfile

As you can see, the website hasn’t been built yet, so there’s no public folder.

However, we know that after the automatic build process, a public folder will be generated. This means we can already prepare the Docker image. In this example, we’re using Nginx as the web server, but feel free to use any other tool that fits your needs.

Create a Dockerfile in the root of your repository:

touch Dockerfile
vim Dockerfile # or nano

Then, paste the following content:

FROM nginx:alpine
COPY public /usr/share/nginx/html
EXPOSE 80

Make sure to commit your changes:

git add Dockerfile
git commit -m "feat: added Dockerfile"

GitHub Workflow

Our CI pipeline will consist of just a few steps:
First, we’ll build the Hugo site. Then, we’ll use the Dockerfile to package it into an image and push it to a container registry.

In this example, we’re using Docker Hub, so anyone can try it out easily. Of course, you can replace it with any other registry you prefer.

First, create the .github/workflows folder to store your pipeline files:

mkdir -p .github/workflows

Then create the workflow file:

cd .github/workflows
touch hugo.yml
vim hugo.yml

Paste the following content:

name: Build and Push Hugo Site to Docker Hub

env:
  IMAGE_NAME: <your_image_name>

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Install Hugo
        run: |
          HUGO_VERSION=0.128.0
          wget -O hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
          sudo dpkg -i hugo.deb          

      - name: Install Dart Sass
        run: sudo snap install dart-sass

      - name: Build Hugo site
        run: hugo --minify --baseURL "/"

      - name: Install cosign
        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
        with:
          cosign-release: 'v2.2.4'

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
        with:
          images: docker.io/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: |
            docker.io/${{ env.IMAGE_NAME }}:latest
            docker.io/${{ env.IMAGE_NAME }}:${{ github.sha }}            
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Finally, replace the placeholder with your own image name, for example:

env:
  IMAGE_NAME: user123/hugo-static-website

Before pushing, note that the workflow uses two secrets:
DOCKER_USERNAME and DOCKER_PASSWORD.

You need to configure these secrets in your GitHub repository:

  1. Go to Settings → Secrets and variables → Actions.
  2. Under Repository secrets, add:
    • DOCKER_USERNAME → Your Docker Hub username
    • DOCKER_PASSWORD → Your Docker Hub password or personal access token
GitHub Secrets

After completing this setup, push your changes:

git add .
git commit -m "ci: added hugo pipeline"
git push

Docker Hub

Now, you should find your new image on Docker Hub:

GitHub Secrets

Run the website on your localhost with:

docker run -d -p 80:80 <user/image:tag> # example: user123/hugo-static-website:latest

Now, you can visit your website on http://localhost

Make your repository private on Docker Hub

If you don’t host an image registry by yourself, you could also make your docker image repository private to make the website unavailable for other docker hub users.

To achive this, Docker Hub go to manage repositories and choose your repository. On Settings you will find the Visibility settings. Change the visbility to private.