Generare varianti di job con strategy e matrix
La chiave jobs.<job_id>.strategy.matrix definisce una o più variabili, ciascuna con un elenco di valori: Actions genera un job per ogni combinazione possibile. Con version: [10, 12, 14] e os: [ubuntu-latest, windows-latest] ottieni sei job, e l’ordine in cui vengono avviati segue l’ordine in cui hai dichiarato le variabili.
Due chiavi correggono il prodotto cartesiano. include aggiunge proprietà alle combinazioni già generate quando non c’è conflitto, e crea combinazioni nuove quando il conflitto c’è. exclude rimuove le combinazioni che corrispondono alle proprietà indicate. La confusione tipica, e l’esame ci insiste, è credere che include serva a filtrare: non filtra, aggiunge. Se vuoi togliere una casella dalla griglia ti serve exclude; se vuoi arricchire una sola variante con una variabile extra, per esempio attivare la code coverage solo sulla combinazione Node 22 su Ubuntu, ti serve include.
fail-fast vale true per impostazione predefinita: appena un job della matrice fallisce, GitHub annulla gli altri job in corso e in coda. Per una matrice diagnostica, dove vuoi sapere quali combinazioni si rompono e non solo che qualcosa si è rotto, imposta fail-fast: false. Per tollerare il fallimento di una singola variante nota come instabile, continue-on-error sul job è più chirurgico. max-parallel limita quanti job della matrice girano contemporaneamente: serve quando i job toccano una risorsa esterna condivisa, un ambiente di staging o un servizio con rate limit, che non regge il parallelismo pieno.
Il costo di una matrice troppo grande è concreto e ha un tetto documentato: una matrice può generare al massimo 256 job per esecuzione del workflow, sia su runner GitHub-hosted sia self-hosted. Molto prima di quel tetto, però, incontri il limite di concorrenza del tuo piano: i job eccedenti restano semplicemente in coda, allungando il tempo di feedback. Tre sistemi operativi per quattro versioni di runtime per due database fanno ventiquattro job a ogni push.
Le immagini dei runner cambiano sotto i piedi
Le etichette con suffisso -latest non sono stabili nel tempo. Oggi ubuntu-latest corrisponde a Ubuntu 24.04, windows-latest a Windows Server 2025, macos-latest a macOS 15 su architettura arm64. La documentazione avverte esplicitamente che le immagini -latest sono le ultime immagini stabili fornite da GitHub e potrebbero non essere la versione più recente rilasciata dal vendor del sistema operativo. Ubuntu 20.04 non compare più tra le etichette supportate.
La conseguenza pratica è che un workflow scritto tempo fa con runs-on: ubuntu-latest oggi gira su un’immagine diversa, con toolchain, Python di sistema e pacchetti preinstallati diversi. Il passaggio di windows-latest a Windows Server 2025 e quello di macos-latest ad arm64 hanno rotto build che davano per scontato l’ambiente precedente. Per le pipeline di rilascio conviene indicare l’etichetta versionata (ubuntu-24.04, windows-2025) e trattare l’aggiornamento come una modifica deliberata. La matrice è lo strumento giusto per gestire la transizione: metti nella stessa matrix sia l’etichetta pinnata sia quella -latest, con fail-fast: false, e vedi in anticipo che cosa si romperà quando l’alias si sposterà.
Service container con porte e health check
jobs.<job_id>.services avvia container Docker di supporto per la durata del job: un database, una cache, un broker. Vengono creati puliti a ogni job e distrutti alla fine. Girano solo su runner Linux con Docker disponibile e non possono essere dichiarati dentro una composite action.
Il punto che l’esame verifica quasi sempre è la rete, perché il comportamento cambia radicalmente. Se il job gira dentro un container, job e servizi condividono una rete bridge definita dall’utente: raggiungi il servizio usando l’etichetta del servizio come hostname, e non serve mappare alcuna porta perché i container sulla stessa rete si espongono tutte le porte a vicenda. Se invece il job gira direttamente sulla macchina runner, il servizio non espone nulla: devi mappare esplicitamente con ports e connetterti a localhost o 127.0.0.1. Con una porta host assegnata casualmente recuperi il valore dal contesto job.services.<service_id>.ports[<host_port>].
La chiave options accetta le opzioni di docker create, con l’eccezione di --network e --entrypoint, che non sono supportate. È lì che si mettono gli health check, e senza di essi gli step partono prima che il servizio accetti connessioni.
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
Il job attende che il container risulti healthy prima di eseguire gli step. Per Redis il comando equivalente è --health-cmd "redis-cli ping", con gli stessi parametri di intervallo, timeout e retry.
Anchor e alias: riuso dentro lo stesso file
La documentazione di Actions descrive due soli marcatori. Un anchor, marcato con &, identifica un blocco di contenuto che vuoi riusare; un alias, marcato con *, ripete quel contenuto in un’altra posizione. “Ripete” è la parola decisiva: l’alias inserisce l’intero contenuto ancorato, non una porzione, e la sintassi documentata non prevede alcun modo di riusare un blocco sovrascrivendone parzialmente una chiave. Se ti serve una variante, ancora il sottoinsieme davvero comune e scrivi per esteso ciò che cambia. Anchor e alias inoltre valgono solo all’interno dello stesso documento YAML: non attraversano i confini del file.
Qui c’è una discrepanza da conoscere. La study guide dell’esame cita anche le merge key, scritte <<, che nella specifica YAML fondono una mappa dentro un’altra. La documentazione del prodotto non le documenta tra le funzionalità di Actions. Riconoscine il nome se compaiono in una domanda, ma non costruirci sopra un workflow di produzione: l’unico riuso descritto dalla documentazione GitHub dentro un singolo file è quello di & e *, mentre il riuso supportato fra file passa da reusable workflow e composite action.