Come Docker ha cambiato la mia vita da sviluppatore (in meglio)
Da Alessandro Lanni
Dopo quasi 3 anni di utilizzo, vi racconto come questo prodotto ha cambiato, in meglio, la mia vita da sviluppatore e, allo stesso tempo, vi condivido i comandi che più di frequente utilizzo e qualche “trucchetto” che ho imparato nel tempo. Una sorta di mini-guida, scritta in italiano, a Docker.
Dalle difficoltà nascono le opportunità
31 Dec 2018: la roadmap di PHP annunciava che PHP 5.6 sarebbe passata in EOL in questa data. Bel problema, ho sviluppato tonnellate di codice PHP e non ero per niente contento di alcune delle deprecazioni introdotte dalla versione 7. Dovevo trovare un modo per testare velocemente il mio codice, senza dover per forza ricompilare le librerie del mio ambiente di sviluppo, per tutte le nuove versioni di PHP.
Il 9 Marzo 2017 avevo preso parte alla 5° edizione di Incontro Devops Italia, edizione molto piena, particolarmente interessante, in cui per la prima volta vidi Docker in azione. Ovviamente non capii da subito le vere potenzialità del prodotto, mi ci volle qualche mese e, guarda caso, una necessità trainante.
Quindi, tornando alle applicazioni PHP e penando a Docker mi dissi: e se provassi a buttare giù questa immagine per testare il mio codice? Iniziai a trattare quella immagine come se fosse una Virtual Machine: un errore gravissimo! Non avevo ancora capito la vera essenza di Docker.
Dopo un periodo di formazione iniziale (necessario), continuando a guardare Docker con occhio critico, ma allo stesso tempo interessato, riuscii ad avviare i primi container grazie ai quali il mio codice PHP 5.6 veniva processato con un interprete PHP 7.0. Potrebbe sembra nulla, ma questa breve e intensa esperienza mi fece guadagnare diversi skills: in primis, quelli relativi al codice, riuscendo finalmente a definire quali applicazioni potevano essere adeguate e quali dovevano essere rimpiazzate da nuovo codice. In più, Docker, mi ha abilitato un nuovo modo di vedere la progettazione del software, aprendo la strada a temi come la decomposizione dei sistemi, micro-servizi oltre che continuous delivery e continuous improvement.
I 4 pilastri di Docker
Grazie a questa intuizione e da questa breve esperienza, Docker ha cambiato radicalmente la mia vita da sviluppatore. Insieme ai miei colleghi, oggi, sto convertendo i nostri ambienti di sviluppo, strutturando l’automazione degli ambienti di staging, dei test e del controllo della qualità del codice.
Nel trasferire le principali caratteristiche di Docker ai miei collaboratori, mi sono reso conto conto che, nell’operatività quotidiana e per favorire la “migrazione” dalle vecchie abitudini, i concetti chiave da capire empiricamente sono:
1 - il file system di un container Docker è effimero
Si, un container dispone di un layer scrivibile, è vero. Ma e’ altrettanto vero che in un processo ordinario, è pensabile spegnere, rimuovere e riavviare infinite volte un determinato container. Scrivere i nostri dati nel layer scrivibile, quindi, significherebbe perdere il dato non appena si cancella il container.
Ne consegue che é di fondamentale importanza distinguere i tipi di dati prodotti dalla nostra applicazione (che siano un set di files o dei dati di un DB, il concetto non cambia). Quelli effimeri, appunto, potremmo lasciarli anche in una path del file system del container. Quelli importanti, invece, vanno preservati e gestiti mediante i volumi, soluzione che Docker ci offre per la persistenza del dato.
Detto questo, se pensi di creare un’applicazione e usare Docker, accertati di definire bene i path per cui prevedi di salvare dati, é una informazione che trasporterai dal codice alle immagini e dalle immagini ai container.
2 - tratta una immagine come se fosse una classe
Le assonanze tra la OOP e Docker sono tantissime. In prima battuta, analizzando la definizione di immagine:
Un’immagine Docker è un modello, di sola lettura, che contiene una serie di istruzioni per la creazione di un contenitore che può essere eseguito.
Il pensiero va subito alla definizione di Classe:
Una Classe è un modello di codice, estensibile, che fornisce valori iniziali per lo stato e implementazioni di comportamento, utilizzate per la creazione di oggetti.
Se poi pensi che le immagini di Docker possono essere estese, le similitudini aumentano ancora.
Abituati a pensare alle immagini Docker, allo stesso modo in cui pensi alle Classi.
3 - tratta un container come se fosse un oggetto
Nel paragrafo precedente ti ho esposto il mio modo di pensare alle immagini: trattarle come Classi. Ne va di conseguenza che abituarti a pensare ai container come allo stesso modo in cui pensi agli oggetti (alle istanze della classe) è un passo.
I container, in esecuzione, posso restarci fin quando il nodo è attivo (teoricamente all’infinito), ma possono anche essere eseguiti solo per il tempo necessario: pensa ad uno scirpt PHP, normalmente resta in esecuzione per il tempo necessario a completare i task per cui è stato progettato; se lo andrai ad incapsulare in una immagine Docker, il conseguente container, in esecuzione, ci rimarrà fino al termine dello script.
4 - dai peso alle immagini
Per quanto sia comodo buttare ogni cosa utile in una immagine, abituati a pensare allo stretto necessario. Una immagine molto pesante non conviene mai, ne per il trasporto ne tantomeno per la sicurezza (piú cose contiene, piú é facile ereditare exploit o bug).
Docker in azione
Come ti dicevo, uso Docker per tanti scopi e in tanti modi differenti. Voglio parlartene fornendoti alcuni esempi:
Sviluppo Applicazione
Per sviluppare una applicazione, ad esempio in PHP, per prima cosa creo una immagine di sviluppo, che conterrà tutto quello che reputo necessario per questa fase di sviluppo.
Il Dockerfile
Come vedi, baso la mia immagine su quella ufficiale di PHP 8.0.11, nella versione Apache (si, c’é apache dentro :-) ). Analizziamo il codice:
Alla riga 5 installo le librerie necessarie affinché la mia applicazione funzioni correttamente (ad esempio PDO). Con un “find e replace” , poi, sostituisco nella configurazione di default di Apache la web-root, che passa da /var/www/html a /var/www/public (public mi piace di più).
Alla riga 16, installiamo xdebug che, per la fase di sviluppo, sará utilissimo per lo step debugging e per il profiling.
Tra la riga 26 e la 29, invece, installo il composer, che userò per la gestione delle dipendenze.
Una volta creato il mio Dockerfile, non posso ancora avviare un container, ma devo prima creare l’immagine che, nel mio esempio, renderó riconoscibile con il tag (nome) alessandrolanni/php8.0-apache-custom. Dal terminale, quindi, dalla directory che contiene il Dockerfile, lancio il comando:
docker build --tag alessandrolanni/php8.0-apache-custom .
Il risultato ottenuto sarà, appunto, una immagine contenente tutti i tools che mi faranno comodo nella fase di sviluppo. Se volessi ottenere la lista delle immagini del mio Docker, usero’ il comando:
docker image ls
Ottenendo questo risultato:

Il codice sorgente
Quello che vedi di seguito è la struttura del mio codice sorgente, nel momento iniziale:

La directory src conterrà tutto il codice sorgente, il Dockerfile lo posizioniamo allo stesso livello. Come vedi utilizzo composer per la gestione delle dipendenze.
L’idea di massima è quella di utilizzare il codice sorgente direttamente nel file system del mio PC (dove ho installato la IDE) e di utilizzare il container per metterlo in esecuzione.
Avvio del container
Con la premessa che ti ho appena fatto, ti mostro come avviare il container, utilizzando l’immagine che ho creato “alessandrolanni/php8.0-apache-custom” e il mio codice sorgente.
E’ molto importante lanciare il comando run dalla stessa directory che contiene Dockerfile e src.
docker run --name medium-demo \
-d \
-v $(pwd)/src:/var/www \
-p 8080:80 \
alessandrolanni/php8.0-apache-custom
Il comando permette, appunto, l’esecuzione di un container; i parametri che ho specificato hanno queste funzioni:
name permette di specificare un nome da attribuire al container, rendendolo riconoscibile tra gli altri (ricorda, il nome e’ univoco, non possono esistere due container con lo stesso nome).
d permette di fare il “detach” del processo, una volta eseguito;
v serve a mappare il file system “host” con quello del container. In pratica chiedo a docker di mappare la directory src del mio PC, alla directory /var/www del container.
p serve a mappare la porta “host” 8080 alla porta 80 del container, quella di Apache. alessandrolanni/php8.0-apache-custom infine, e’ il nome dell’immagine che dovrà essere usata.
I valori dei parametri vengono spesso separati dai due punti “:”; La convenzione vuole che, a sinistra ci sia il valore riferito all’ host e a destra, quello riferito al container
A questo punto, se tutto fila liscio, il container e’ in esecuzione; per verificare che tutto sia in funzione, faccio questo test:
lynx --dump http://localhost:8080/
Ottenendo questa risposta, scontata:
Hello, World!
Come avrai già capito, il file src/public/index.php contiene questo codice:

Con il container avviato, ogni modifica effettuata al codice sorgente, comporterà una risposta immediata, visto che il codice sorgente dell’Applicazione (cartella src) è “mappato” come volume esterno.
Continuo con la fase di sviluppo, quindi avvio l’installazione delle dipendenze; Il file eseguibile del composer, come hai visto dal Dockerfile, viene installato alla creazione dell’immagine. Ma come posso usarlo?
Allo stadio attuale, quindi, ho un container chiamato “medium-demo”, con cui posso interagire. Docker permette l’esecuzione di comandi all’interno del container, mediante il comando exec. Ti spiego come usarlo:
docker exec medium-demo composer install
I parametri del comando sono: il nome del container (medium-demo) e di seguito il comando che voglio eseguire nel container, nel mio caso composer install.
Al termine dell’installazione delle dipendenze mediante composer, il codice sorgente della mia applicazione, cambia forma, acquisendo la cartella “vendor”.
Posso reiterare le operazioni di modifica del codice e di esecuzione dei comandi nel container all’infinito. Posso pensare di usare Docker per eseguire un container in modo persistente, ma posso anche usare Docker per avviare il container, eseguire uno script e successivamente cancellare il container.
Inoltre, se ho l’esigenza di sviluppare applicazioni pensate per funzionare con diverse versioni di PHP, posso usare lo stesso ambiente di sviluppo, creando tante immagini differenti, una per ogni versione di PHP di cui necessito.