Cos'è il trunk based development? Guida pratica per SRE
Riassunto
Il trunk based development è un modello di branching in cui gli sviluppatori integrano piccole modifiche in un unico trunk condiviso almeno una volta al giorno, mantenendolo sempre rilasciabile. Il lavoro incompleto resta spento dietro feature flag o branch by abstraction. Il modello richiede CI veloce, test affidabili e osservabilità legata a ogni release. DORA lo associa a migliori performance di delivery. Funziona male quando la build è lenta o quando le release versionate richiedono branch di manutenzione di lunga durata.
Sono le 3 di notte e il branch di release non fa merge. Quaranta commit, tre settimane di vita, gli stessi file del main. Per capire cos'è il trunk based development partiamo da questo problema: è un modello di branching in cui tutti integrano piccole modifiche in un unico branch condiviso, chiamato trunk o main, almeno una volta al giorno, mantenendo quel branch sempre rilasciabile.
Questa guida è pensata per platform engineer che conoscono già Git. Spiega cosa richiede il modello, da quali rischi protegge e dove, in silenzio, si rompe.
Cosa significa, in termini operativi?
Riduciamo la definizione ai suoi vincoli. Tutti gli sviluppatori integrano in un solo branch. Ogni altro branch vive per ore, non per settimane. Il trunk compila e supera i test a ogni commit, quindi può essere rilasciato in qualsiasi momento.
La pagina delle capacità di DORA fissa dei numeri: tre branch attivi o meno nel repository, merge sul trunk almeno una volta al giorno, nessun code freeze e un ciclo di build e test che dura pochi minuti. Quei numeri sono il punto. Il modello è un budget di feedback loop, non una preferenza di stile sui branch.

I team piccoli a volte committano direttamente sul trunk. I team più grandi usano branch di breve durata e pull request per la review e i controlli di build, ma mai per trattenere il lavoro fuori dall'integrazione. Il sito di riferimento trunkbaseddevelopment.com descrive entrambe le modalità e cita Google, che fa lavorare circa 35.000 sviluppatori su un unico trunk in un monorepo.
Perché i branch di lunga durata falliscono secondo un calendario prevedibile
Un branch di feature è un prestito. Gli interessi sono i conflitti di merge, e crescono a ogni commit che atterra sul main mentre il branch è fuori. Più a lungo vive il branch, più grande è il diff, e più grande è il diff, meno qualcuno lo legge con attenzione.
Ecco cosa succede al tasso di fallimento dei cambiamenti. Un merge da 2.000 righe viene scorso e approvato. Un merge da 60 righe viene letto. I reviewer non sono pigri: razionano l'attenzione. I batch piccoli sono l'unico meccanismo che scala la qualità della review.
Il secondo costo è invisibile fino al rilascio. Due branch che superano ciascuno la CI possono comunque rompersi a vicenda quando si incontrano. Lo si scopre al momento dell'integrazione, il peggiore possibile, con una scadenza addosso.
Di cosa ha bisogno il trunk prima di potersi fidare
Passare al trunk senza le pratiche di supporto è il modo in cui i team tornano ai branch di feature entro un trimestre. Servono quattro cose, prima di tutto.
Una build con i test che girano in meno di circa dieci minuti. Se la CI ne impiega 40, gli sviluppatori accumulano le modifiche in batch, e il batching annulla il modello.
Test che falliscono per motivi reali. Una suite instabile insegna a rilanciare e a fare merge comunque.
Un processo di review rapido e onesto. DORA indica la code review pesante e asincrona come ostacolo ricorrente, perché spinge a raggruppare il lavoro.
Un modo per rilasciare lavoro incompleto in sicurezza. È la sezione successiva.
Se ne manca una, il trunk diventa il posto in cui i guasti si accumulano, invece di quello in cui vengono intercettati.
Come unire lavoro incompleto senza rilasciarlo?
È la domanda che ogni scettico fa, e la risposta è poco spettacolare: si separa il deploy dal rilascio. Il codice arriva in produzione spento, e un flag decide chi lo vede.
// checkout.ts
import { flags } from "./flags";
export async function renderCheckout(user: User) {
if (await flags.isEnabled("new-payment-flow", { userId: user.id })) {
return renderNewPaymentFlow(user); // unito sul trunk, spento di default
}
return renderLegacyCheckout(user);
}Il nuovo flusso viene unito il primo giorno, dietro un flag disattivato. Si accende per gli utenti interni, poi per l'1 per cento, poi per il 10, con un gate sugli SLO a ogni passaggio. Se il tasso di errore supera il budget, il flag si spegne e la modifica viene di fatto annullata senza un nuovo deploy. I team che valutano servizi di feature flag gestiti spesso partono da LaunchDarkly, anche se il pattern funziona con qualunque provider o con uno store di configurazione interno.
La seconda tecnica è il branch by abstraction, per i grandi refactor. Si introduce un'interfaccia, si fanno passare i chiamanti da lì, si costruisce la nuova implementazione dietro di essa e si sostituisce quando è pronta. Nessun branch di lunga durata, nessun merge big-bang.

Il costo, però, va detto con onestà. Ogni flag aggiunto è un condizionale in produzione che qualcuno deve rimuovere. Si sono visti team di 80 ingegneri portarsi dietro qualche centinaio di flag stantii, ciascuno un ramo di codice che nessuno può testare fino in fondo.
Il modo più sano è trattare i flag come elementi a vita breve per contratto. A ogni flag si assegnano un responsabile e una data di scadenza al momento della creazione. Si genera un alert sui flag oltre la scadenza. Il percorso di codice morto si elimina nella stessa pull request che rimuove il flag.
Anche il rischio combinatorio è reale. Dieci flag booleani indipendenti danno 1.024 configurazioni possibili, e non si testeranno mai tutte. Conviene mantenere poche interazioni tra flag e tenere separati i flag di release dagli interruttori operativi di lunga durata, come i kill switch.
Strategie di release e osservabilità
Ci sono due modi comuni per tagliare una release dal trunk, e nessuno richiede un branch di lunga durata.
Rilasciare direttamente dal trunk. Ogni commit verde è un candidato. I bug si correggono in avanti, con un nuovo commit, non applicando una patch a un branch vecchio. È adatto a team con test automatici solidi e deploy veloci.
Tagliare un branch di release appena serve. Si parte da un commit del trunk noto come buono, lo si irrigidisce, lo si rilascia e poi lo si elimina. I fix atterrano prima sul trunk e vengono riportati indietro con cherry-pick. È adatto a team con gate di rilascio più lenti o con approvazioni regolamentate.
La scelta dipende dal tempo di rilevazione. Se un deploy difettoso si nota in cinque minuti e si fa revert in due, si corregge in avanti. Se il mean time to detect è di un'ora, un branch di release offre un punto d'appoggio.

L'osservabilità è l'altra metà del patto. Il trunk based development aumenta la frequenza dei deploy, e quindi il numero di momenti in cui qualcosa può andare storto. Il modello funziona solo se una regressione si vede entro pochi minuti da un merge. Servono tassi di errore, percentili di latenza e saturazione legati a una release specifica, non a una dashboard che qualcuno controlla il lunedì.
Un test utile: dopo un merge, si riesce a rispondere a "questa modifica ha spostato il p99 o il tasso di errore?" senza aprire cinque schede? Se no, non si è pronti a unire dieci volte al giorno.
Qualunque sia lo stack, il requisito è lo stesso: marcatori di deploy sui grafici, alert di burn rate sugli SLO e un percorso di revert che una persona stanca possa eseguire alle 3 di notte senza ragionarci.
Trunk based development contro GitFlow e GitHub Flow
Questi tre modelli si confondono spesso, quindi conviene distinguerli con una proprietà: per quanto tempo il lavoro resta fuori dal branch condiviso.
GitFlow mantiene un branch develop, branch di feature, di release e di hotfix. Il lavoro può restare isolato per settimane. È stato pensato per release versionate e pianificate, e mostra i suoi limiti quando si prova a rilasciare dieci volte al giorno.
GitHub Flow è vicino al trunk. Branch di breve durata, pull request, merge su main, deploy. La differenza che il sito di riferimento evidenzia riguarda soprattutto da dove partono le release.
Il trunk based development è il più rigoroso dei tre sulla durata dei branch, e presuppone flag o branch by abstraction per tutto ciò che non può essere rilasciato in una sola modifica piccola.
Se il team usa già GitHub Flow con branch che vivono meno di due giorni, è più vicino al trunk di quanto pensi. Il divario sta di solito nella disciplina sui flag e nella latenza della review, non negli strumenti.
Quando il trunk based development è la scelta sbagliata
Conviene rinviarlo, o evitarlo, in alcuni casi. Va riconosciuto con onestà in quale di questi ci si trova.
La suite di test richiede un'ora e non si può parallelizzare. Si finirà per fare batch, e il batching rompe il modello.
Si rilasciano artefatti versionati a clienti che restano su versioni vecchie per anni. Servono branch di manutenzione. È legittimo, e si può comunque integrare ogni giorno sul trunk.
Non esiste un sistema di flag e non c'è voglia di costruirlo. Il lavoro a metà finirà nelle release.
Il team non si fida della build. Prima si sistemano i test. Il trunk da solo non risolve nulla.
Il trunk based development non garantisce niente. Il risultato di DORA è una correlazione dai dati del 2016 e del 2017, con team che seguono queste pratiche e mostrano performance di delivery e operative migliori. Non dice che rinominare i branch sistema la pipeline.
Come partire senza una migrazione big-bang
Conviene non annunciare nessuna policy. Prima si misura, poi si restringe. Le metriche DORA vanno strumentate prima di cominciare, altrimenti si discute a sensazione.
Registrare la durata attuale dei branch e la dimensione mediana delle pull request. Sono la baseline.
Fissare un limite, per esempio nessun branch più vecchio di due giorni, e renderlo visibile su una dashboard.
Sistemare la parte più lenta della CI. Se la build dura 30 minuti, nient'altro conta ancora.
Introdurre un flag per una funzionalità reale. Rilasciarla spenta e rimuovere il flag entro uno sprint.
Eliminare i code freeze per ultimi, quando il percorso di revert è stato provato sul campo.
Conviene rivalutare dopo un mese. I numeri che si muovono per primi sono di solito la dimensione delle pull request e il tempo di merge. Il tasso di fallimento dei cambiamenti richiede più tempo e a volte peggiora prima di migliorare, perché i guasti emergono finalmente prima.
Cosa dirà il prossimo post-mortem del modello di branching? Rivedere gli ultimi tre incidenti aiuta: quanti dipendevano da un'integrazione grande e tardiva? Quel conteggio è il caso d'affari più solido che si possa presentare.