Salta al contenuto principale

Deploy di un sito statico con Hugo e GitHub Actions: hosting a costo zero

Indice dei contenuti
I siti statici sono il modo più efficiente per pubblicare contenuti. Nessun server da gestire, nessun costo di runtime, nessuna sorpresa di scalabilità. Con Hugo e GitHub Actions ottieni un blog veloce e versionato, deployato automaticamente a ogni push, a costo effettivamente zero.

Perché Hugo e non Gatsby o Next.js
#

Gatsby ha avuto il suo momento, ma l’ecosistema si è spostato. Il progetto è stato acquisito, la manutenzione è rallentata e la maggior parte dei team che lo usavano è migrata a Next.js o a un generatore statico. Hugo occupa una nicchia diversa: non dipende da React, non richiede Node.js e compila migliaia di pagine in meno di un secondo. Se stai costruendo un blog o un sito di documentazione, e non una vera applicazione React, Hugo è lo strumento giusto.

Questo blog gira su Hugo deployato su GitHub Pages tramite GitHub Actions. Tutto quello descritto qui è il setup reale, non un esempio teorico.

flowchart LR
    A([Scrivere Markdown]) --> B[git push su main]
    B --> C[Workflow GitHub Actions]
    C --> D[hugo --minify]
    D --> E[Deploy su GitHub Pages]
    E --> F([Sito online])

Installare Hugo
#

Hugo è distribuito come singolo binario. L’approccio consigliato su macOS è Homebrew:

terminale
brew install hugo

Su Linux, scarica il binario extended dalla pagina delle release. La versione extended è richiesta dai temi che utilizzano SCSS.

terminale
hugo version
# hugo v0.155.3+extended darwin/arm64
Importante

Installa sempre la variante extended. Molti temi dipendono dalla compilazione SCSS, disponibile solo nella build extended.

Creare un nuovo sito
#

terminale
hugo new site my-blog
cd my-blog

Questo crea la struttura di directory di base:

my-blog/
  archetypes/    # template per i contenuti
  content/       # i tuoi file markdown vanno qui
  layouts/       # override dei template
  static/        # file copiati cos come sono (favicon, CSS, immagini)
  themes/        # i submodule dei temi vanno qui
  config.toml    # configurazione del sito

Aggiungere un tema come submodule Git
#

I temi in Hugo vengono aggiunti come submodule git in modo da poterli aggiornare indipendentemente dai contenuti. Questo e il passaggio critico che le pipeline CI spesso sbagliano.

terminale
git init
git submodule add https://github.com/nunocoracao/blowfish.git themes/blowfish

Poi fai riferimento al tema in config.toml:

config.toml
baseURL = "https://tuonomeutente.github.io/"
theme   = "blowfish"
Nota

Il baseURL deve corrispondere esattamente all’URL di deployment. Un errore qui causa link rotti nella build di produzione.

Scrivere i post
#

Struttura delle directory
#

Tutti i post del blog si trovano in content/posts/. Un file Markdown per ogni post.

terminale
hugo new posts/il-mio-primo-post.md

Front matter
#

Hugo legge i metadati dal blocco front matter in cima a ogni file:

content/posts/il-mio-primo-post.md
---
title: "Il mio primo post"
date: 2025-06-01T10:00:00+01:00
draft: false
tags: ["go", "aws", "devops"]
---

Il tuo contenuto qui.

Imposta draft: true mentre scrivi. I post in bozza vengono esclusi dalle build di produzione per default. Usa hugo server -D in locale per vedere le bozze.

Sviluppo locale
#

terminale
hugo server -D

Questo avvia il server di sviluppo su http://localhost:1313 con live reload. Le modifiche a contenuti o template si riflettono nel browser istantaneamente senza bisogno di aggiornare la pagina.

Il workflow di GitHub Actions
#

Questo e il workflow usato da questo blog. Si attiva al push su main, compila il sito con Hugo extended e fa il deploy su un repository GitHub Pages separato usando un personal access token.

.github/workflows/gh-pages.yaml
name: Publish github page

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true   # necessario per i submodule del tema
          fetch-depth: 0     # necessario per .GitInfo e .Lastmod

      - name: Setup Hugo
        run: |
          wget -q https://github.com/gohugoio/hugo/releases/download/v0.147.6/hugo_extended_0.147.6_linux-amd64.deb
          sudo dpkg -i hugo_extended_0.147.6_linux-amd64.deb

      - name: Build
        run: hugo --minify

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        if: github.ref == 'refs/heads/main'
        with:
          personal_token: ${{ secrets.PERSONAL_TOKEN }}
          external_repository: tuonomeutente/tuonomeutente.github.io
          publish_branch: main
          publish_dir: ./public

Alcuni aspetti da tenere a mente.

submodules: true e obbligatorio. Senza di esso, la directory del tema e vuota e Hugo fallisce silenziosamente o con un errore confuso.

fetch-depth: 0 recupera l’intera storia git. Serve se il tuo tema usa .GitInfo o .Lastmod per mostrare le date di ultima modifica.

hugo --minify riduce le dimensioni dell’output HTML, CSS e JavaScript. Usalo sempre in produzione.

Deploy su un repository separato
#

Il pattern sopra usa external_repository e personal_token. Torna utile quando il repository dei contenuti e privato o quando vuoi tenere gli artefatti di build separati dalla sorgente.

Deploy sul branch gh-pages dello stesso repository. Setup piu semplice, funziona bene per i siti di progetto.

.github/workflows/deploy.yaml
- uses: peaceiris/actions-gh-pages@v4
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: ./public

Deploy su un repository separato tuonomeutente.github.io. Necessario per le GitHub user pages (che devono trovarsi in un repo col tuo nome utente).

.github/workflows/deploy.yaml
- uses: peaceiris/actions-gh-pages@v4
  with:
    personal_token: ${{ secrets.PERSONAL_TOKEN }}
    external_repository: tuonomeutente/tuonomeutente.github.io
    publish_branch: main
    publish_dir: ./public

Crea un personal access token con scope repo e salvalo come PERSONAL_TOKEN nei secrets del repository sorgente.

Dominio personalizzato
#

Per usare un dominio personalizzato, crea un file CNAME in static/:

static/CNAME
blog.tuodominio.com

Poi configura il DNS con un record CNAME che punta blog.tuodominio.com a tuonomeutente.github.io. GitHub gestisce HTTPS automaticamente tramite Let’s Encrypt.

Configurazione di Hugo
#

Un config.toml minimale per questo setup:

config.toml
baseURL            = "https://tuonomeutente.github.io/"
defaultContentLanguage = "en"
theme              = "blowfish"
pagination.pagerSize = 10

enableRobotsTXT  = true
buildDrafts      = false
buildFuture      = false
googleAnalytics  = "G-XXXXXXXXXX"

[markup.goldmark.renderer]
  unsafe = true

[markup.highlight]
  noClasses = true
  style     = "nord"
Dimenticare --minify nel passaggio di build
Il server di sviluppo non minifica. Se copi un comando hugo dal workflow locale, deployerai asset non minificati. La differenza nelle dimensioni delle pagine puo essere significativa con molti post. Usa sempre hugo --minify in CI.
Impostare il baseURL sbagliato
Se baseURL non corrisponde all’URL di deployment, tutti i link e gli asset saranno rotti in produzione. Il server locale non intercetta questo problema perche sovrascrive baseURL automaticamente. Verifica eseguendo hugo --minify in locale e controllando i link in public/index.html.
Submodule mancante nel checkout CI
actions/checkout@v4 non recupera i submodule per default. Senza submodules: true, la directory themes/ e vuota. Hugo fallira o produrra un sito bianco a seconda della configurazione.
Post in bozza deployati in produzione
I post con draft: true vengono esclusi solo se non passi -D al comando di build. Il problema si presenta quando dimentichi di cambiare il flag prima di fare il push. Prendi l’abitudine di controllare hugo list drafts prima di fare merge su main.
Non impostare fetch-depth: 0
Per default actions/checkout esegue un clone superficiale. Questo fa si che .GitInfo e .Lastmod restituiscano valori vuoti nei template. Se il tuo tema mostra le date di ultima modifica, imposta fetch-depth: 0.

Se vuoi approfondire uno qualsiasi di questi argomenti, offro sessioni di coaching 1:1 per ingegneri che lavorano su integrazione AI, architettura cloud e platform engineering. Prenota una sessione (50 EUR / 60 min) o scrivimi a manuel.fedele+website@gmail.com.

Articoli correlati