C’è un’idea che circola tra gli sviluppatori ma che raramente esce dalla loro bolla. GitHub Actions, lo strumento che GitHub mette a disposizione per testare e pubblicare software, è in realtà un computer Linux gratuito che chiunque può accendere a comando o a orari programmati.
Chi sviluppa lo usa per la cosiddetta CI/CD, cioè per compilare il codice, eseguire i test a ogni modifica e pubblicare le nuove versioni in automatico. Lo stesso motore, però, può eseguire qualsiasi script, ed è qui che diventa interessante per tutti gli altri. Non serve comprare un VPS, non serve tenere un Raspberry Pi acceso in un cassetto, non serve lasciare il PC in funzione tutta la notte. Basta un account GitHub gratuito e un file di testo scritto nel modo giusto.
Ogni volta che il workflow parte, GitHub avvia una macchina virtuale pulita, esegue i tuoi comandi e la spegne. In questa guida vedremo come trasformare GitHub Actions in un cron nel cloud, ovvero uno script che controlla una pagina web al posto tuo e ti manda una notifica sul telefono se qualcosa cambia, con il codice completo e pronto da adattare.
Vedremo anche la tecnica del git scraping, che usa il repository stesso come archivio storico dei dati, e soprattutto le trappole che nessuno ti racconta, come i ritardi del cron e la regola dei 60 giorni che spegne le automazioni in silenzio. Alla fine avrai un piccolo servizio funzionante, senza aver speso un euro.
Un computer Linux in prestito
Prima di scrivere codice conviene capire cosa stai ricevendo in prestito. Ogni volta che un workflow parte, GitHub crea una macchina virtuale chiamata runner. Sui repository pubblici si tratta di una VM Linux con 4 vCPU e 16 GB di RAM, mentre sui privati la dotazione standard scende a 2 vCPU e 7 GB. In entrambi i casi la macchina arriva già carica di software preinstallato, tra cui Python, Node.js, Git, curl e ffmpeg, tutti pronti all’uso senza installare nulla.

La VM è usa e getta, cioè vive solo per la durata del lavoro e poi viene distrutta insieme a tutto quello che contiene. Per questo, come vedremo, i dati da conservare vanno salvati nel repository o spediti altrove.
Il capitolo costi merita chiarezza, perché è qui che l’offerta sorprende. Sui repository pubblici i minuti sono illimitati: GitHub si riserva di intervenire in caso di abusi evidenti (tipo il classico mining di criptovalute, che è esplicitamente vietato), ma per automazioni personali oneste non esiste un contatore da tenere d’occhio. Sui repository privati il piano Free include 2.000 minuti Linux al mese e 500 MB di spazio per gli artifact, i file che un workflow può mettere da parte.
Attenzione però a un dettaglio, perché i minuti si contano in equivalente Linux. Un minuto su runner Windows ne consuma 2, un minuto su macOS addirittura 10. Se resti su ubuntu-latest, come faremo in questa guida, 2.000 minuti bastano per un controllo di 2 minuti ogni ora, tutti i giorni, con margine di riserva.
A inizio 2026 GitHub ha inoltre ritoccato al ribasso le tariffe dei minuti extra (fino al 39% in meno) e ha introdotto il runner ubuntu-slim a singola CPU per i compiti leggeri, segno che l’azienda spinge proprio verso questo tipo di utilizzi, brevi ma frequenti. Per i nostri scopi la parola d’ordine resta una: gratis, purché il lavoro sia fatto di scatti rapidi e non di servizi sempre accesi. Un workflow può girare al massimo 6 ore di fila, quindi l’idea di usarlo come server permanente muore sul nascere, ed è giusto così.
Anatomia di un workflow, il file YAML comanda tutto
Un’automazione su GitHub Actions è un file di testo in formato YAML, salvato nel repository dentro la cartella .github/workflows/. Il nome del file è libero, la posizione no. Se lo metti altrove, GitHub semplicemente lo ignora. Il file risponde a tre domande, ovvero quando partire, su che macchina girare e cosa eseguire. Questo è lo scheletro minimo di un cron nel cloud:
name: Controllo quotidiano
on:
schedule:
- cron: "17 7 * * *" # ogni giorno alle 7:17 UTC
workflow_dispatch: # aggiunge il pulsante "Run workflow"
jobs:
controlla:
runs-on: ubuntu-latest
steps:
- name: Saluta
run: echo "Sono attivo e sono le $(date)"
La sezione on definisce i grilletti. schedule usa la classica sintassi cron a cinque campi, cioè minuto, ora, giorno del mese, mese e giorno della settimana. La stringa "17 7 * * *" significa quindi “alle 7:17 di ogni giorno”, mentre "*/30 * * * *" significa “ogni 30 minuti”.
Due avvertenze pratiche. Gli orari sono sempre in UTC, quindi d’estate l’Italia è due ore avanti rispetto a quello che scrivi. Conviene inoltre evitare orari “tondi” come il minuto 0, perché è il momento in cui mezzo mondo su github ha programmato i propri workflow e i ritardi potrebbero allungarsi. Il secondo grilletto, workflow_dispatch, è il tuo migliore amico durante i test, perché fa comparire un pulsante nella scheda Actions del repository, così puoi lanciare l’automazione a mano senza aspettare l’orario programmato.
La sezione jobs descrive il lavoro vero e proprio. runs-on: ubuntu-latest chiede una VM Linux standard, e ogni voce sotto steps è un passo eseguito in sequenza. Può essere un comando shell con run, oppure un’azione già pronta con uses. Le azioni sono blocchi riutilizzabili pubblicati dalla community.
actions/checkout@v4, per esempio, scarica il contenuto del tuo repository dentro la VM, cosa indispensabile se il workflow deve eseguire uno script che hai scritto tu. Una volta capito questo schema, hai capito tutto, perché il resto è riempire gli step con i comandi che lanceresti sul tuo PC.
Sorveglia una pagina con avvisi su smartphone
Passiamo alla pratica con un’esigenza che prima o poi capita a tutti, cioè sapere se una pagina web è cambiata. Può essere la pagina di un prodotto esaurito, un bando, la data di uscita di un software, l’annuncio di una prevendita.
Lo script che segue scarica la pagina, ne calcola l’impronta digitale (un hash) e la confronta con quella salvata al giro precedente. Se sono diverse, ti arriva una notifica push tramite ntfy, il servizio gratuito di notifiche di cui abbiamo già parlato su queste pagine, che non richiede registrazione ma solo l’app installata sul telefono e iscritta a un canale.
Crea nel repository un file controlla.py con questo contenuto:
import hashlib
import pathlib
import requests
URL = "https://www.esempio.it/pagina-da-sorvegliare"
CANALE_NTFY = "https://ntfy.sh/scegli-un-nome-lungo-e-segreto"
html = requests.get(URL, timeout=30, headers={
"User-Agent": "Mozilla/5.0 (monitoraggio personale)"
}).text
nuovo_hash = hashlib.sha256(html.encode()).hexdigest()
memoria = pathlib.Path("ultimo_hash.txt")
vecchio_hash = memoria.read_text().strip() if memoria.exists() else ""
if nuovo_hash != vecchio_hash:
memoria.write_text(nuovo_hash)
requests.post(CANALE_NTFY,
data="La pagina è cambiata, vai a controllare!".encode("utf-8"),
headers={"Title": "Avviso da controlla.py",
"Click": URL})
print("Cambiamento rilevato, notifica inviata.")
else:
print("Nessuna novità.")
Poi crea il workflow .github/workflows/segugio.yml:
name: Segugio
on:
schedule:
- cron: "23 6,12,18 * * *" # tre controlli al giorno
workflow_dispatch:
permissions:
contents: write
jobs:
controlla:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Installa le dipendenze
run: pip install requests
- name: Esegui il controllo
run: python controlla.py
- name: Salva lo stato nel repository
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add ultimo_hash.txt
git diff --cached --quiet || git commit -m "Aggiorna stato del controllo"
git push
L’ultimo step risolve il problema della memoria. Dato che la VM viene distrutta a fine corsa, l’hash va salvato nel repository con un commit automatico, e la riga permissions: contents: write dà al workflow il permesso di farlo. Il comando git diff --cached --quiet || è un piccolo trucco per committare solo se c’è davvero qualcosa di nuovo.

Un’avvertenza: su pagine molto dinamiche, piene di banner e contenuti che ruotano, l’hash dell’intera pagina cambia a ogni visita e genera falsi allarmi. In quei casi conviene estrarre solo la porzione che ti interessa, per esempio con la libreria BeautifulSoup e un selettore CSS mirato sul prezzo o sul titolo, e calcolare l’hash solo su quella. Se invece devi usare password o chiavi API, non scriverle mai nello script: vanno nei Secrets del repository (Settings, poi Secrets and variables, quindi Actions) e si leggono come variabili d’ambiente.
Git scraping, il repository diventa la memoria storica dei dati
Il commit automatico dell’esempio precedente apre la porta a una tecnica più ambiziosa, nota come git scraping. L’idea è semplice ma potente. Invece di salvare soltanto un hash, il workflow scarica dei dati veri (un listino, una classifica, le disponibilità di un servizio, un bollettino) e li committa nel repository a ogni esecuzione.
Se i dati non sono cambiati, Git non registra nulla. Se sono cambiati, nasce un commit che fotografa esattamente cosa è cambiato e in che momento. Dopo qualche settimana ti ritrovi, senza aver configurato alcun database, con una cronologia completa e consultabile: basta scorrere i commit o usare git diff per vedere come un prezzo o una lista sono cambiati nel tempo.
Rispetto all’esempio di controlla.pycambia poco. Lo script salva un file di dati invece dell’hash, e il messaggio di commit può includere la data per rendere la cronologia più leggibile:
- name: Scarica e salva i dati
run: |
curl -s "https://api.esempio.it/dati.json" -o dati.json
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add dati.json
git diff --cached --quiet || git commit -m "Dati del $(date -u +'%Y-%m-%d %H:%M')"
git push
Il formato migliore per questi file è JSON o CSV con un ordinamento stabile, perché i diff restano piccoli e leggibili. Se l’API restituisce i campi in ordine casuale, vale la pena riordinarli nello script, altrimenti ogni commit sembrerà stravolgere tutto anche se è cambiata una virgola.
Con il tempo puoi costruire sopra questa base analisi vere e proprie. Scaricare il repository in locale e dare in pasto la cronologia a uno strumento come DuckDB, che abbiamo già visto all’opera sui file CSV e JSON, permette di rispondere a domande come “in quali giorni della settimana questo prezzo scende più spesso?“. A quel punto non hai più un semplice promemoria, ma un piccolo osservatorio personale su un pezzetto di web che ti interessa. E tutto continua a girare da solo, anche con il tuo computer spento.
Cosa sapere prima di fidarti ciecamente
GitHub Actions come cron personale funziona bene, ma ha caratteristiche che è meglio conoscere prima, perché scoprirle dopo costa tempo e frustrazione. La prima riguarda la puntualità, dato che l’orario del cron è un desiderio, non una promessa. GitHub esegue i workflow programmati quando ha risorse disponibili, e ritardi di 5, 15 o anche 30 minuti sono del tutto normali nelle ore di punta. Per un controllo prezzi non cambia nulla, ma se ti serve precisione al minuto questo non è lo strumento giusto. Come già accennato sopra, scegliere minuti “strani” tipo 17 o 23 invece di 0 riduce sensibilmente le attese.
La seconda trappola è la più subdola. Se sul repository non arrivano commit per 60 giorni, GitHub disattiva automaticamente i workflow programmati. Ricevi un’email di avviso, facile da perdere, e da quel momento la tua automazione tace senza alcun errore visibile. Aprire issue o creare release non conta, perché solo i commit sul branch principale azzerano il timer.
La buona notizia è che le automazioni viste in questa guida si salvano da sole, dato che ogni cambiamento rilevato produce un commit. Se però il tuo workflow non scrive mai nel repository, conviene aggiungere uno step keepalive che ogni tanto committa un file marcatore, oppure segnarsi di passare dalla scheda Actions una volta al mese a riattivare tutto con un clic.
Restano le questioni di buon vicinato. Un runner condiviso non è un lasciapassare: rispetta i termini di servizio dei siti che interroghi, mantieni frequenze ragionevoli (tre controlli al giorno bastano quasi sempre, uno al minuto è quasi sempre eccessivo) e preferisci le API ufficiali se esistono.
Ricorda inoltre che nei repository pubblici tutto è visibile, log compresi. Mai stampare a schermo chiavi o password, che vanno tenute nei Secrets, e valuta un repository privato se l’oggetto del monitoraggio è personale. Infine, i log delle esecuzioni si conservano per un tempo limitato. Se un giorno il workflow fallisce, la scheda Actions ti mostra l’errore esatto, ma non aspettarti di trovare traccia di ciò che è successo sei mesi prima.
Un cron gratuito per chi non vuole gestire un server
L’aspetto che trovo più interessante di questo approccio non è il risparmio in sé, ma il cambio di prospettiva. GitHub Actions abbassa quasi a zero il costo di ingresso dell’automazione personale. Un VPS, per quanto economico, va scelto, pagato, aggiornato e messo in sicurezza. Un Raspberry Pi va comprato e alimentato, e il PC acceso di notte consuma e fa rumore. Qui invece l’infrastruttura sparisce dal problema, e resta solo la parte divertente, cioè decidere cosa vuoi che succeda e scriverlo in uno script.
Certo, non è uno strumento per compiti che richiedono puntualità assoluta, non può ospitare servizi sempre attivi, e la regola dei 60 giorni impone un minimo di attenzione. Per la fascia di mezzo, però, quella dei controlli periodici, dei promemoria intelligenti e delle raccolte dati, è difficile trovare qualcosa di più conveniente e affidabile allo stesso tempo, con il bonus non trascurabile che ogni automazione resta versionata, documentata e replicabile per natura.
C’è anche un valore formativo. Costruire il tuo primo file python di controllo ti fa toccare con mano YAML, cron, Git e le notifiche push, ovvero gli stessi mattoni che tengono in piedi le pipeline professionali. Parti dall’esempio di questa guida, adattalo a una pagina che ti sta a cuore e lascialo lavorare. La prima notifica inaspettata sul telefono vale più di mille spiegazioni.













