Hai costruito una bella app in Flutter. Ora arriva la parte che nessuno racconta: il rilascio. Firme, store, numeri di versione, segreti... e quell'unica persona che “sa come si fa la build”. Vediamo come una pipeline CI/CD — con Fastlane sul mobile — trasforma il rilascio di app Flutter (e di software in generale) in un sistema affidabile, veloce e indipendente da chi lo lancia.
Dal classico meme “It works on my machine” alla build riproducibile
C'è un momento, in quasi ogni team che sviluppa software, in cui il rilascio diventa un rito sciamanico. Esiste una persona che “sa come si fa la build”, magari è anche l'unico ad avere le chiavi di firma delle app sul suo portatile, conosce a memoria la password di queste, ricorda quali variabili d'ambiente vanno impostate prima di lanciare la build, eccetera. Finché quella persona è in ufficio, tutto fila liscio. Il giorno in cui è in ferie... il disastro! Il rilascio si blocca, e con lui va in ferie anche il prodotto.
Questa non è un'ipotesi astratta: è purtroppo la fotografia di moltissime realtà che magari scrivono anche ottimo codice. Il problema infatti non è tecnico, è strutturale. Spesso, anche a livello accademico, si sorvola su come gestire il software e soprattutto come portare qualcosa da un ambiente di sviluppo a un sistema di produzione. Il sapere come deployare il software spesso risiede nella testa di una singola persona (un senior developer o un sistemista) invece di essere infuso nel sistema.
Una pipeline CI/CD serve esattamente a spostare quel sapere fuori dalle conoscenze del singolo che ci ha già sbattuto la testa mille e più volte, e a far confluire il tutto in uno strumento ripetibile, verificabile e accessibile. In questo articolo proverò a spiegarvi perché conviene adottare una pipeline CI/CD, partendo da problemi reali che anche noi di Codebaker abbiamo incontrato in passato e da come li abbiamo risolti.
Cosa intendiamo davvero per CI/CD
L'acronimo è abusato, quindi vale la pena fissare i termini. Continuous Integration significa che ogni modifica al codice viene integrata di frequente nel ramo principale (venendo ovviamente validata). Questo include pulizia del codice, dei commit, compilazione, test, analisi statiche e dinamiche del software. Continuous Deployment significa che da quella stessa modifica si arriva, in modo automatizzato, a un artefatto pronto al rilascio e in alcuni casi fino alla pubblicazione effettiva sugli store o sul server.
Il cuore di tutto è un servizio che, a fronte di un evento (un commit, un tag, un merge su branch di staging, eccetera) esegue una sequenza di passi “sempre uguale”, descritta in un file versionato insieme al codice. Non parliamo di una wiki con gli step da seguire o di una qualche astrusa documentazione: parliamo di codice. Se possiamo sintetizzare tutto in un blocco di codice, allora questo sarà leggibile, modificabile e, soprattutto, riproducibile da chiunque e da qualsiasi macchina adatta a quel tipo di task. (Ehm... Apple e le build iOS...)
Il valore nell'automatizzare le build
Immaginiamo una pipeline che costruisce un'app in Flutter e la carica sugli store. Il processo, fatto a mano, è una catena lunga e fragile: bisogna generare gli artefatti, e già qui si aprono le prime scelte — sub package per staging, chiavi per Firebase, localizzazione, librerie interne vs pacchetti pub, e chi più ne ha più ne metta. Inoltre, ancor prima della compilazione ci sono operazioni come l'allineamento del numero di versione che richiedono attenzione. Questi pacchetti poi bisogna firmarli con le chiavi giuste per Android e iOS, caricarli sui rispettivi store con i metadati corretti (foto, changelog, policy update). Ogni passo può rompersi in un modo diverso su macchine diverse e con umani diversi.
Per esempio, se mai avete provato a buildare un'app su un Mac ARM saprete già il repertorio: dipendenze native che si comportano diversamente rispetto a x86, versioni di Ruby installate sotto emulazione invece che native, toolchain con versioni diverse fra le varie macchine in ufficio. Il risultato è il classico “da me funziona tutto correttamente, perché da te no?”. Non è incompetenza di nessuno: è semplicemente l'effetto di ambienti non riproducibili.
Una pipeline rompe questo circolo vizioso proprio perché definisce un solo ambiente di build canonico, anche se la macchina che esegue il rilascio non è sempre la stessa: per la prima volta ci sarà un sistema con le stesse versioni degli SDK, stesse variabili d'ambiente, eccetera — e spesso tutto ciò non viene garantito nemmeno da un pub.lock, per esempio.
La domanda “chi fa la build?” smette di avere importanza, perché la risposta sarà sempre: la pipeline. Lo sviluppatore non firma più nulla dal proprio portatile; pusha il codice e il sistema fa il resto, allo stesso modo, per tutti.
Questo ha un effetto collaterale prezioso: i problemi di configurazione vengono scoperti una volta sola e risolti per sempre. Quando un build di release fallisce perché il keystore di firma non viene trovato nel percorso giusto, o perché un flavor di staging era configurato male, quella correzione finisce nel file di pipeline. Da quel momento il problema non si ripresenterà mai più, su nessuna macchina. Il debugging non è sprecato: diventa patrimonio del sistema.
Fastlane: il Flutter per Flutter (e non solo)
Nel mondo mobile, lo strumento che ha imposto uno standard de facto per questa automazione è Fastlane. Nasce per risolvere esattamente i problemi descritti sopra: firmare, costruire, versionare e pubblicare app iOS e Android senza dover ricordare decine di comandi diversi. Ci sono già operazioni come, ad esempio, fastlane match che astraggono alcune operazioni che altrimenti risulterebbero astruse.
La forza di Fastlane sta nel concetto di lane: una “corsia” è una sequenza di passi con un nome. Si scrive una volta lane :deploy_staging e da quel momento chiunque, o qualsiasi server di CI, può lanciarla con un comando solo. Dentro la lane si eseguono azioni già pronte: costruire l'app bundle in release, salire di versione seguendo il tag su Git, pubblicare l'app su Google Play o App Store Connect con changelog corretti e tanto altro.
Quello che prima era un documento di dieci passi manuali — “genera l'AAB, controlla che sia firmato in release e non in debug, verifica il package name, carica sul track interno, aggiorna le note di rilascio” — diventa una manciata di righe di codice versionate. E come ogni codice, possono essere revisionate, testate e migliorate in modo incrementale.
La gestione dei segreti: il punto dove tutto può andare storto
Qui arriviamo al tema più delicato. Una pipeline di rilascio ha bisogno di segreti: la password del keystore, l'alias della chiave, il file di credenziali dello store, eventuali token di accesso a servizi esterni e altri valori critici da includere nella build. Sono le chiavi del regno. Se finiscono nel posto sbagliato, il danno è permanente (o perlomeno fin quando non ve ne accorgete e ruotate tutte le credenziali).
L'approccio corretto separa nettamente due mondi:
- Il codice, che descrive come si usa un segreto. Per esempio, una configurazione di firma che legge la password da una variabile, o un percorso che punta a un file di credenziali. Questo sta nel repository, perché non rivela nulla.
- I valori dei segreti, che vivono solo nell'ambiente sicuro del CI. I sistemi di CI/CD offrono variabili protette o vault dedicati: valori cifrati a riposo, mascherati nei log, iniettati nell'ambiente di build solo al momento dell'esecuzione e solo per i job che ne hanno diritto.
Il file con i segreti, in questo modello, viene generato dalla pipeline a ogni run a partire dalle variabili protette, e non esiste da nessun'altra parte. Il keystore vero risiede in una posizione sicura sull'agent di build, fuori dal checkout del codice. Lo sviluppatore non vede mai i valori in chiaro, e questo è esattamente l'obiettivo: ridurre al minimo il numero di persone che hanno accesso al materiale critico, senza per questo impedire a nessuno di lanciare un build.
Il principio guida è il minimo privilegio: ogni persona e ogni job devono poter fare esattamente ciò che serve, niente di più. La pipeline diventa l'unico punto in cui i segreti vengono usati, e quindi anche l'unico in cui questi devono essere esposti.
Proteggere la produzione: i branch non sono tutti uguali
C'è un secondo livello di sicurezza, complementare alla gestione dei segreti, che a un occhio superficiale potrebbe sembrare fuori da questo topic, e riguarda chi può far partire una build di produzione. Anche con i segreti ben custoditi, se chiunque può pushare codice sul branch che fa partire il deploy di produzione, il rischio resta enorme, sia per una questione di sicurezza che per il semplice errore umano.
La risposta è la protezione dei branch. Si configura il sistema di versionamento in modo che il branch principale non accetti push diretti dagli sviluppatori. Ogni modifica deve passare da una pull request, superare i controlli e ricevere l'approvazione di almeno un'altra persona. In questo modo il codice che arriva in produzione è sempre stato visto da più di un paio d'occhi e ha sempre sicuramente passato i test.
Si lega poi il deploy di produzione a eventi controllati: per esempio solo a un merge sul ramo protetto, o solo alla creazione di un tag di versione, magari con un'approvazione manuale esplicita come ultimo controllo. Gli ambienti di staging o di sviluppo, invece, possono essere anche più permissivi: è anzi consigliato permettere agli sviluppatori di buildare in continuazione, anche a ogni commit, perché un errore lì non ha conseguenze sugli utenti reali e fa parte del normale ciclo di sviluppo di un software.
Il risultato è una separazione netta tra poter contribuire e poter rilasciare in produzione. Tutto il team ovviamente deve poter lavorare, proporre modifiche, lanciare build di staging. Ma il cancello finale verso gli utenti è governato da regole esplicite, scritte e applicate automaticamente, non dalla fiducia o dalla memoria di qualcuno. Anche in questo caso il punto non è non fidarsi dei colleghi, ma non far dipendere la sicurezza dalla disciplina individuale.
Eliminare il lavoro manuale inutile
Tirando le fila dell'articolo, gran parte del valore di una pipeline CI/CD si riduce a un principio sano: ciò che una macchina può fare in modo affidabile, non dovrebbe farlo una persona. Non perché le persone non siano capaci, ma perché il lavoro manuale ripetitivo è il terreno fertile per gli errori, ed è un costo nascosto che cresce a ogni rilascio, a ogni modifica, e in alcuni casi porta ancora più debito tecnologico del codice stesso.
Ci si dimentica di incrementare manualmente il numero di versione. Ci si sbaglia a copiare a mano il keystore nella cartella giusta (sempre che non teniate tutto sul Desktop). Si perde tempo (sinceramente tempo speso male) ad allineare le note di rilascio tra ambienti. Ognuna di queste micro-attività, presa singolarmente, sembra da poco: messe insieme, e moltiplicate per la frequenza dei rilasci, diventano ore sottratte allo sviluppo vero e una fonte costante di incidenti evitabili.
Automatizzandole, succedono tre cose. Il rilascio diventa veloce: ciò che richiedeva mezza giornata di attenzione si risolve in pochi minuti non presidiati (dopo, ovviamente, i dovuti test di qualità). Diventa affidabile: la macchina non si distrae e non salta i passaggi. E diventa frequente: quando rilasciare costa poco e fa poca paura, si rilascia spesso, in piccoli incrementi, che sono più facili da verificare e meno rischiosi dei grandi rilasci accumulati nel tempo.
Conclusione
Una pipeline CI/CD, con strumenti come Fastlane a fare da motore sul versante mobile, non è un lavoro da grandi aziende o un capriccio da ingegneri perfezionisti. È il modo in cui si trasforma il rilascio del software in un sistema: riproducibile, sicuro, indipendente da chi lo lancia e dalla macchina su cui gira.
I tre pilastri si sostengono a vicenda. Un ambiente di build canonico rende le build agnostiche rispetto a chi le esegue. Una gestione disciplinata dei segreti tiene le chiavi fuori dai posti sbagliati e fuori dai singoli laptop degli sviluppatori. La protezione dei branch separa chi contribuisce da chi rilascia in produzione. Tutto ciò insieme elimina il lavoro manuale inutile e la dipendenza da quell'unica persona che “sa come si fa”.
Il momento giusto per costruire questa infrastruttura non è quando il rilascio si rompe la prima volta. È prima: quando il prodotto è ancora piccolo (o magari nemmeno esiste) e la pipeline si scrive in un pomeriggio, correggendo subito gli errori. Ma anche se sei già nella fase del rito sciamanico, vale comunque la pena: ogni problema che risolvi una volta nella pipeline è un problema che non ti tormenterà mai più.
Davide Antonio Amodio
Vuoi automatizzare i tuoi rilasci?
In Codebaker progettiamo pipeline CI/CD e infrastrutture che rendono i rilasci veloci, sicuri e indipendenti dalla persona di turno. Se vuoi uscire dal “rito sciamanico” del deploy, parliamone.
Contattaci