Sviluppo web

GitHub Actions per un sito: la pipeline minima

Un workflow che costruisce, conserva l'artefatto e pubblica solo da main: versione dei pacchetti, cache, permessi e i punti in cui si rompe.

In sintesi

  • `permissions: contents: read` toglie al token tutto ciò che il job non usa.
  • La cache di npm si invalida con l'hash del lockfile, non con la data.
  • `needs` più `if` sul ref impediscono a un pull request di pubblicare.

GitHub Actions per un sito è un file YAML in .github/workflows: un job che costruisce, uno che pubblica e un permesso in sola lettura. Le versioni misurate sono checkout 7.0.1 e setup-node 7.1.0.

Il file minimo

name: ci
on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v7
        with:
          name: dist
          path: dist
          retention-days: 7

concurrency annulla il run precedente sullo stesso ref; permissions: contents: read riduce il token al solo checkout. Senza quel blocco il token porta con sé i permessi di default del repository.

La cache che si invalida da sola

      - uses: actions/cache@v6
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: npm-

La chiave contiene l'hash del lockfile: cambi una dipendenza, cambia la chiave, la cache si ricrea. Una chiave fissa lascerebbe pacchetti vecchi per mesi, e il sintomo sarebbe una build che passa solo in CI.

Pubblicare solo da main

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v7
        with:
          name: dist
          path: dist
      - name: Publish
        env:
          SSH_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          install -m 600 /dev/null key
          printf "%s" "$SSH_KEY" > key
          rsync -az --delete -e 'ssh -i key' dist/ [email protected]:/var/www/site/

needs impone che il build sia verde, if limita il job al ramo principale. Su un pull request da fork i secret non arrivano, quindi il job salta e la CI resta verde senza esporre la chiave.

Artefatti e diagnosi

gh run list --limit 5
gh run watch
gh run download -n dist

upload-artifact conserva dist per sette giorni; gh run download -n dist riporta quei file in locale senza rifare la build. gh run watch segue il run fino alla fine, con i log del passo che fallisce.

Matrice e tempi

    strategy:
      fail-fast: false
      matrix:
        node: [22, 24]

La matrice duplica il job per ogni versione di Node. fail-fast: false lascia finire le altre combinazioni, così vedi se il problema è solo sulla versione vecchia.

Limite dichiarato

I minuti si pagano sui repository privati, e ogni matrice li moltiplica. cancel-in-progress uccide anche un deploy a metà se arriva un push durante il rilascio: separa build e deploy in due workflow se il rilascio dura più di qualche minuto.

Dove continuare

Tre componenti che finiscono nel build: input, spinner e la griglia delle icone; il resto è in components/lib e nelle 1.556 anteprime. Gli errori di build sono in errori di build comuni, l'ordine delle cartelle in App Router nel 2026. Il confronto fra Vite e Next per un sito marketing è in Next.js vs Vite.

Domande frequenti

Quanto costa GitHub Actions per un sito piccolo?

Sui repository pubblici i minuti sono gratuiti; su quelli privati il piano Free include 2.000 minuti al mese. Una build di due minuti per push sta largamente dentro quel tetto.

Dove metto la chiave SSH per il deploy?

In un secret del repository o dell'environment, mai nel file YAML. Il secret si passa al passo come variabile d'ambiente, e la chiave si scrive su disco con permessi 600.

Perché il job di deploy viene saltato sui pull request?

Perché la condizione if limita il job al ramo main, e perché i secret non sono disponibili per i fork. È il comportamento voluto: il pull request verifica il build, non pubblica.

Meglio cache di setup-node o actions/cache?

setup-node con cache: npm copre il caso normale e richiede due righe. actions/cache serve quando devi conservare una cartella che setup-node non conosce, per esempio un browser o un binario scaricato a mano.

Posso lanciare il workflow a mano?

Sì, aggiungendo workflow_dispatch fra i trigger. Da terminale il comando è gh workflow run ci.yml, e gh run watch segue l'esecuzione.