Rack1 Networks
Hosting
Basic Shared Web HostingAccelerated Web HostingStatic Web HostingWordPress Optimized Hosting Managed Cloud (coming soon) Compare all plans
AI Chatbots
AI Chatbots for your site
Platform
The Rack1 Platform Our control panel Security Edge CDN One-click installer
Company
Blog About Rack1 Documentation Support Acceptable Use Policy View plans

Deploying a Hugo site from GitHub Actions

A complete workflow that builds your site on every push and publishes the output, with the two settings that trip people up.

An abstract representation of a build pipeline publishing files to the edge

The nice thing about a static site is that deployment is just copying files. The nicer thing is that you can stop doing the copying yourself.

This is the workflow we recommend for Hugo. The shape is identical for Astro, Eleventy, Jekyll or anything else that produces a folder.

The workflow

name: Build and deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive   # themes are often submodules
          fetch-depth: 0          # Hugo needs history for .Lastmod

      - uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: latest
          extended: true          # required if your theme uses Hugo Pipes

      - run: hugo --minify

      - name: Publish
        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
        with:
          server: ftp.example.com
          username: ${{ secrets.RACK1_USER }}
          password: ${{ secrets.RACK1_PASS }}
          protocol: ftps
          local-dir: ./public/
          server-dir: /public_html/

The two settings people get wrong

Clone the full history. With fetch-depth: 0 unset, Actions clones a single commit. Hugo derives .Lastmod from git history, so without the full history every page claims to have been modified at the moment of the build. If you output modification dates in your templates or your sitemap, they will all be wrong.

Use the extended binary. Without extended: true, the standard Hugo binary cannot compile Sass. If your theme uses Hugo Pipes for its stylesheets, the build fails with an error about resources.ToCSS that does not obviously point at the cause.

The trailing slash matters

local-dir: ./public/ with the trailing slash uploads the contents of public. Without it you get a public folder inside your web root and a site that 404s. The same applies to rsync.

Deploying over SSH instead

If you prefer rsync, the same build step applies and only the publish changes:

rsync -avz --delete ./public/ you@ssh.example.com:~/public_html/

Note the ~/. Over SSH your web root sits inside your home directory, so an absolute /public_html points at the root of the filesystem and fails. Over FTP the account is chrooted and /public_html is correct. It is a small difference that costs people an afternoon.

Keep the secrets in secrets

Never commit credentials. Both examples read from the encrypted secret store (${{ secrets.NAME }} on GitHub, masked variables on GitLab). If a credential has ever been committed, rotate it rather than deleting the commit.

Put this into practice.

Every Rack1 plan includes the edge CDN, daily offsite backups and auto-scaling compute, from $2.99 a month.