GitHub Actions Workflow to Build and Publish a Static Website

Use GitHub Actions to build a Hugo static site and deploy it to a server via SSH and rsync. Includes workflow YAML, SSH secrets, and known_hosts.

This guide walks you through using GitHub Actions to build a Hugo static site and deploy it to a remote server whenever you push to a branch. Hugo turns Markdown (and other content) into static HTML; GitHub Actions runs the build on push and syncs the public/ output to your server via SSH and rsync.

GitHub Actions in brief

GitHub Actions automates parts of your workflow (testing, building, deploying) in response to events such as push, pull request, or merge. A workflow is a YAML file (under .github/workflows/) that defines one or more jobs, each made up of steps. For a static site, a typical job checks out the repo, installs Hugo, builds the site, then deploys the build artifact (e.g. with rsync over SSH).

Prerequisites

  • A Hugo site in a GitHub repository (see Blog with Hugo static site generator if you need a starting point).
  • A destination server reachable by SSH (VPS, cloud VM, or your own host).
  • SSH key pair: the public key on the server (~/.ssh/authorized_keys), the private key stored as a GitHub secret.

Store SSH credentials in GitHub Secrets

You need the GitHub Actions runner to SSH into your server. Use a dedicated deploy key (or a key pair used only for this):

  1. Generate an SSH key pair (if you don’t have one):
    ssh-keygen -t ed25519 -C "github-actions-deploy" -f deploy_key -N ""
  2. Add the public key to the server: append deploy_key.pub to ~/.ssh/authorized_keys on the deploy user’s account.
  3. In the GitHub repo: Settings → Secrets and variables → Actions → New repository secret.
  4. Add these secrets (use uppercase with underscores; names are referenced in the workflow):
    • SSH_PRIVATE_KEY – contents of the private key file (the whole file, including -----BEGIN ... KEY----- and -----END ... KEY-----).
    • SSH_USER – SSH username on the server (e.g. deploy or ubuntu).
    • SSH_HOST – server hostname or IP (e.g. example.com or 203.0.113.10).

The workflow will use these to run rsync over SSH.

Example workflow: build Hugo and deploy with rsync

Create .github/workflows/hugo-deploy.yml in your repo (adjust the branches and deploy path to match your setup):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
name: Hugo build and deploy

on:
  push:
    branches: [master]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
          fetch-depth: 0

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: "latest"

      - name: Clean public directory
        run: rm -rf public

      - name: Build
        run: hugo -D

      - name: Install SSH key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          known_hosts: "optional-placeholder"

      - name: Add known hosts
        run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy with rsync
        run: rsync -avz --delete ./public/ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/var/www/citizix/

What each part does

  • on.push.branches – runs the workflow when you push to master (change to main if that’s your default branch).
  • Checkout – clones the repo and submodules (needed for Hugo themes as submodules); fetch-depth: 0 helps Hugo’s .Lastmod etc.
  • Setup Hugo – installs Hugo so the hugo command is available.
  • Clean public – removes any existing public/ so the build is fresh.
  • Build – runs hugo -D (drafts included; drop -D for production).
  • Install SSH key – loads SSH_PRIVATE_KEY into the runner’s ~/.ssh. The known_hosts value in the action can be a placeholder if you add the host in the next step.
  • Add known hosts – runs ssh-keyscan -H ${{ secrets.SSH_HOST }} and appends the result to ~/.ssh/known_hosts so the first SSH/rsync connection doesn’t prompt for host key verification.
  • Deploy – rsyncs ./public/ to the server. --delete removes files on the server that no longer exist in public/. Change /var/www/citizix/ to your actual web root.

If you prefer not to use the “Add known hosts” step, you can precompute the host key and store it in a secret (e.g. SSH_KNOWN_HOSTS) and write it to ~/.ssh/known_hosts in the workflow; using ssh-keyscan in the job is simpler and keeps the host key in sync with the server.

Verifying

  • After a push to the configured branch, open the repo’s Actions tab and confirm the workflow run succeeds.
  • On the server, check that /var/www/citizix/ (or your path) contains the updated static files and that the site loads correctly in a browser.

Summary

You set up a GitHub Actions workflow that builds a Hugo site and deploys it with rsync over SSH. You stored the private key, SSH user, and host in GitHub Secrets, and used the Install SSH key action plus ssh-keyscan so the runner can connect without host key prompts. Adjust branches, deploy path, and hugo flags to match your environment. For other deployment targets (e.g. GitHub Pages or GKE), you can reuse the same checkout-and-build steps and swap the deploy step for the appropriate action or CLI.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy