Chi tiene la chiave del deploy
In GitHub Actions un job che pubblica qualcosa non si distingue tecnicamente da un job che esegue i test: entrambi sono passi in un workflow. La differenza la introduci tu, dichiarando un environment. Un environment è un contenitore nominato (tipicamente staging, production) a cui appendi protection rules e a cui associ secret e variabili dedicate. Il job lo richiama con la chiave environment, nella forma breve con il solo nome oppure nella forma estesa con name e url, dove url è il collegamento che compare nella pagina Deployments, nel grafo della run e nella timeline della pull request.
Le protection rules disponibili nelle impostazioni dell’environment sono tre famiglie. I required reviewers sono un elenco di persone o team con almeno accesso in lettura al repository: il job resta in attesa finché uno di loro non lo sblocca, e basta una sola approvazione anche se ne hai indicati diversi. Il wait timer impone una pausa forzata prima che il job parta, utile come finestra di ripensamento su una release; il tempo di attesa non viene conteggiato come tempo fatturabile, quindi non è un modo per bruciare minuti. Le deployment branch policies decidono da quali ref si può deployare: nessuna restrizione, solo i branch protetti, oppure un elenco di pattern per branch e tag confrontati con GITHUB_REF. Attenzione a un dettaglio che ricorre nelle domande: nei pattern il carattere jolly non attraversa la barra, quindi un pattern pensato per releases/* non copre releases/2026/hotfix. Esistono inoltre le custom deployment protection rules, che delegano il via libera a un sistema esterno tramite una GitHub App.
Due opzioni valgono da sole metà del sotto-obiettivo. La prima è Prevent self-review, che impedisce a chi ha innescato la run di approvare il proprio deploy anche se figura fra i reviewer: senza quella spunta, l’autore della modifica autorizza sé stesso e il gate diventa decorativo. La seconda è Allow administrators to bypass configured protection rules, attiva per impostazione predefinita: se il requisito è che nessuno scavalchi il controllo, va deselezionata esplicitamente. Il reviewer agisce dalla run con il pulsante Review deployments, può lasciare un commento e sceglie fra Approve and deploy e Reject; il rifiuto fa fallire il workflow, non lo mette in pausa.
I secret arrivano solo dopo il sì
Qui sta la confusione tipica. Molti immaginano che il runner riceva tutti i secret all’avvio del job e che l’approvazione sia solo un semaforo visivo. Non è così: un job che punta a un environment con reviewer richiesti non può leggere i secret di quell’environment finché l’approvazione non arriva. Lo stesso vale per le variabili di environment esposte tramite il contesto vars. È la ragione per cui le credenziali di produzione vanno messe a livello di environment e non di repository: un secret di repository è visibile a qualunque job, gate o non gate.
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
concurrency:
group: deploy-production
cancel-in-progress: false
steps:
- run: ./deploy.sh
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Una novità che vale la pena conoscere è deployment: false dentro la forma estesa: il job ottiene secret e variabili dell’environment ma GitHub non crea l’oggetto deployment, così un job di test che ha bisogno di credenziali non sporca la cronologia dei rilasci.
Cache e artifact non sono la stessa leva
Caching e artifact vengono spesso confusi perché entrambi salvano file, ma rispondono a domande diverse e la documentazione è esplicita nel dire che non sono intercambiabili. La cache serve a riusare file rigenerabili fra run diverse, tipicamente le dipendenze; l’artifact serve a conservare l’output di un job dopo la fine della run o a passarlo a un altro job.
Sulla cache pesano due meccanismi da ricordare. Il primo è la chiave: key cerca la corrispondenza esatta, restore-keys fornisce prefissi di ripiego valutati in ordine, e l’output cache-hit è vero solo per la corrispondenza esatta. Il secondo è lo scope: le cache sono condivise in base al branch o al tag, non all’identità del workflow. Una run può leggere le cache del proprio branch, del branch di default e del branch base di una pull request, ma non quelle di branch figli o fratelli. Gli eventi a bassa fiducia ottengono solo accesso in lettura. Quando lo spazio del repository si esaurisce, l’eviction elimina le cache dalla data di ultimo accesso più vecchia: se le pull request generano cache enormi, quelle del branch di default vengono create e distrutte di continuo, il famoso cache thrashing. Si controlla da Actions, sezione Caches, oppure con gh cache list e gh cache delete.
Sugli artifact la leva è retention-days nell’action di upload, che può solo restare entro il limite fissato da repository, organizzazione o enterprise. Alzare la retention su artifact pesanti è uno dei modi più silenziosi di far crescere il conto dello storage.
Smettere di pagare run già superate
La chiave concurrency accetta un group e l’opzione cancel-in-progress, disattivata per impostazione predefinita. Su un branch di feature, un gruppo costruito con nome del workflow e ref più cancel-in-progress: true annulla il build precedente appena arriva un nuovo push: nessuno paga per verificare un commit già sorpassato. Su produzione l’impostazione va rovesciata, perché annullare un deploy a metà è peggio che accodarlo; puoi anche rendere condizionale l’annullamento con un’espressione. I nomi dei gruppi non distinguono maiuscole e minuscole.
Restano le leve di dimensionamento. La matrice moltiplica i job: prima di aggiungere una dimensione, chiediti se ogni combinazione porta informazione nuova, e usa exclude per potare i casi inutili, max-parallel per non saturare la concorrenza del piano e fail-fast per fermare il resto quando una combinazione cade. Aggiungi sempre un timeout-minutes esplicito sui job: il valore predefinito è molto alto e un job bloccato consuma finché non lo raggiunge. Infine i larger runners, che offrono più CPU, RAM e disco: sono fatturati al minuto e i minuti inclusi dei repository privati non si applicano, quindi conviene assegnarli tramite runner group solo ai job che davvero ne traggono vantaggio. L’esame ragiona per compromessi, non per valori assoluti: aspettati di dover scegliere la combinazione corretta fra gate umano, cache e concurrency dato uno scenario, non di ricordare una soglia a memoria.