Home/Docker
Laboratorio · Manuale di Docker

Docker

L'ingegnere dell'imballaggio universale.

27 esercizi7 Parti5 appendici
Il codice colore indica il livello:FondamentaleIntermedioAvanzatoCloud Architect
scarica gli esercizi
git clone --filter=blob:none --sparse https://github.com/calmict/book_labs.git
cd book_labs
git sparse-checkout set docker/ed1
calm@calmict:~$ cat docker/argomenti
Parte 1

L'illusione dell'isolamento

  • 01Il container a mani nudeFondamentale
  • 02Le sei stanzeFondamentale
  • 03Il tetto e l'OOMIntermedio
  • 04L'overlay a mani nudeIntermedio
Parte 2

L'architettura del motore

  • 05La catena e il custodeIntermedio
  • 06La ricetta OCIIntermedio
  • 07Morire con graziaIntermedio
Parte 3

L'arte della creazione

  • 08La lista di caricoIntermedio
  • 09Il piano di caricoFondamentale
  • 10Il comandante e gli ordiniIntermedio
  • 11Il magazzino e la nave leggeraIntermedio
  • 12La nave in produzioneAvanzato
Parte 4

Persistenza e stato

  • 13Ciò che resta a terraFondamentale
  • 14Tre modi di stivareIntermedio
  • 15Il numero sul badgeAvanzato
Parte 5

I labirinti della rete

  • 16Il cavo e la centralinaAvanzato
  • 17Il centralino privatoAvanzato
  • 18Attaccato o staccatoAvanzato
  • 19Sulla banchina, e oltre l'orizzonteCloud Architect
Parte 6

Orchestrazione locale

  • 20La flotta in un foglioIntermedio
  • 21Il segnale di via liberaAvanzato
  • 22La cassaforte, non il post-itAvanzato
Parte 7

Day-2, sicurezza e hardening

  • 23Re nella propria stanzaCloud Architect
  • 24Le chiavi giuste, non tutteCloud Architect
  • 25Il diario di bordo e i quadrantiAvanzato
  • 26La scatola nera del container mutoCloud Architect
  • 27Pulire la stiva, guardare il mareCloud Architect
calm@calmict:~$ ls docker/esercizi/
01Il container a mani nudeFondamentale

Cosa costruisci

Il viaggio comincia smontando la prima illusione: quella di aver acceso una piccola macchina. In questo laboratorio costruisci un container senza Docker, con il solo comando unshare, e dimostri con i tuoi occhi ciò che il capitolo annuncia — un container non è una nave a sé, è un normalissimo processo Linux a cui il kernel ha raccontato una realtà ristretta. Prima di imbarcarti sul motore di Docker, tocca con mano di cosa è fatta la stiva.

Obiettivi

  • Costruire un container a mani nude, senza Docker, con unshare (1.4).
  • Dimostrare dall'interno di essere il processo numero 1 di un nuovo mondo (1.4).
  • Isolare l'hostname con un UTS namespace, senza toccare quello dell'host (1.2, 1.4).
  • Smascherare l'illusione dall'esterno: stesso kernel, namespace diverso (1.5).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap01/solution
./run.sh

Esito atteso

  • manibnude.sh costruisce il container con i flag corretti (TODO 1).
  • Dall'interno la shell è PID 1 e l'hostname è nave-cargo (TODO 2).
  • Dall'host, l'hostname è intatto e l'inode del PID namespace è diverso da quello interno (TODO 3).
  • run.sh stampa OK 1..4 e ALL CHECKS PASSED, incluso il controllo di contrasto: senza --pid il PID interno non è più 1.
02Le sei stanzeFondamentale

Cosa costruisci

Nel capitolo 1 hai staccato un processo dall'elenco degli altri con un solo flag, e quel processo si è ritrovato PID 1 di un mondo tutto suo. Ma quel flag era una porta su un'intera famiglia di isolamenti. In questo laboratorio le apri quasi tutte insieme — hostname, processi, mount, rete, e la mappatura degli utenti — e dimostri, stanza per stanza, che l'isolamento di un container non è una parete unica, ma la somma di più viste che il kernel accetta di non far vedere.

Obiettivi

  • Costruire un processo isolato in più namespace contemporaneamente, senza Docker e senza sudo (2.1).
  • Dimostrare ogni stanza con la sua prova: PID (PID 1), UTS (hostname), MNT (mount privato), NET (stack quasi muto), USER (root finto) (2.2-2.7).
  • Usare l'inode in /proc/self/ns come metro: inode diverso = mondo separato (2.1).
  • Vedere che togliere un flag toglie una sola stanza, non tutte (2.5).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap02/solution
./run.sh

Esito atteso

  • lestanze.sh apre i namespace corretti (TODO 1) e crea il mount privato (TODO 2).
  • host.txt registra gli inode dell'host (TODO 3).
  • Dall'interno: PID 1, hostname sei-stanze, marker presente, rete con la sola loopback, uid 0.
  • run.sh stampa OK 1..6 e ALL CHECKS PASSED, incluso il contrasto: senza --net il processo torna a condividere la rete dell'host.
03Il tetto e l'OOMIntermedio

Cosa costruisci

I namespace del capitolo 2 decidono *cosa* un processo vede. Ma manca l'altra metà dell'isolamento, e senza di essa un container sarebbe un vicino pericoloso: *quanto* può consumare. In questo laboratorio imponi a mano un tetto di memoria a un processo e lo costringi a sfondarlo, per vedere con i tuoi occhi l'OOM killer intervenire — e leggere quell'exit code 137 che ti tornerà davanti in produzione. Tutto rootless, perché la delega di systemd (§3.7) ti dà un pezzo dell'albero dei cgroup senza bisogno di sudo.

Obiettivi

  • Imporre un tetto di memoria a un cgroup con systemd-run --user, senza sudo (3.4, 3.7).
  • Provocare l'OOM killer e riconoscere la sua firma: exit code 137 (3.5).
  • Dimostrare che il tetto isola il danno: senza tetto la stessa allocazione è innocua (3.4).
  • Collegare il 137 alla causa (128 + 9 = SIGKILL), la diagnosi del capitolo 26 (3.5).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap03/solution
./run.sh

Esito atteso

  • L'array CAP impone MemoryMax=40M e MemorySwapMax=0 (TODO 1).
  • greedy_capped_rc registra l'exit code del vorace sotto il tetto (TODO 2).
  • greedy_uncapped_rc registra l'exit code del vorace senza tetto (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED: il vorace col tetto è ucciso (137), il frugale sopravvive (0), il vorace senza tetto sopravvive (0).
04L'overlay a mani nudeIntermedio

Cosa costruisci

Ci resta un'ultima illusione da smontare, la più convincente: quando entri in un container e digiti ls, vedi bin, etc, usr — sembra un'altra installazione Linux. Nel capitolo 2 hai intuito il come (un MNT namespace con una radice diversa); qui monti a mano il pezzo che rende la cosa *efficiente*: il Copy-on-Write con OverlayFS. Impilerai layer in sola lettura, ci scriverai sopra, e vedrai la magia — si copia una sola pagina, solo quando la scrivi. Tutto rootless, dentro i namespace del capitolo 2.

Obiettivi

  • Montare un OverlayFS a mano: due lowerdir in sola lettura più un upperdir scrivibile (4.2, 4.3).
  • Dimostrare il Copy-on-Write: scrivere un file "di sola lettura" lascia il lower intatto e copia nell'upper (4.3).
  • Vedere che i lower condivisi rendono due container quasi gratuiti, con upper privati (4.4).
  • Chiudere il cerchio del capitolo 2: l'overlay come radice dentro un MNT namespace (4.3).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap04/solution
./run.sh

Esito atteso

  • overlay.sh monta container A e registra la vista fusa (TODO 1).
  • Scrivendo su a.txt, il lower resta intatto e la modifica è nell'upper (TODO 2).
  • Container B, con upper diverso, vede l'originale (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
05La catena e il custodeIntermedio

Cosa costruisci

Nella Parte 1 hai azionato a mano namespace, cgroup e overlay. Ora apri il cofano dello strumento che li aziona per te — e la prima sorpresa è che «Docker» non è un programma solo, ma una catena di componenti che si passano il lavoro. In questo laboratorio segui una richiesta dal socket fino al kernel: parli al demone a mani nude, mappi la catena, e dimostri che il genitore del container è lo shim, non il demone. È la prova tecnica dietro una promessa importante — puoi aggiornare Docker senza uccidere i tuoi container.

Obiettivi

  • Parlare al demone direttamente sul socket con curl: la CLI è solo un client API (5.2).
  • Verificare che la stessa API elenca i container, come farebbe docker ps (5.1, 5.2).
  • Mappare la catena e provare che il genitore del container è un containerd-shim, non dockerd (5.3, 5.5).
  • Capire perché sopra lo shim c'è systemd/containerd e non il demone: la base del live-restore (5.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap05/solution
./run.sh

Esito atteso

  • lacatena.sh registra la versione ottenuta dal socket (TODO 1).
  • Registra il genitore (uno shim) del container (TODO 2).
  • Registra il nonno (systemd/containerd, non dockerd) (TODO 3).
  • run.sh stampa OK 1..4 e ALL CHECKS PASSED.
06La ricetta OCIIntermedio

Cosa costruisci

Nel capitolo 5 hai visto una catena fatta di anelli distinti. Ma perché tanta frammentazione? La risposta è una parola: standard. In questo laboratorio scendi all'ultimo anello e costruisci ed esegui un container OCI a mani nude con runc — senza Docker nel ciclo. Genererai il config.json, la ricetta esatta in cui tutta la Parte 1 si condensa, e vedrai che runc non fa altro che eseguirla alla lettera. Cambierai la ricetta e il container cambierà: perché il config.json è il container.

Obiettivi

  • Costruire un bundle OCI a mani nude ed eseguirlo con runc, senza Docker nel ciclo (6.3).
  • Leggere nel config.json i meccanismi della Parte 1 elencati come dati (namespace) (6.3).
  • Dimostrare che runc è un esecutore fedele: cambiando la ricetta cambia il container (6.3).
  • Capire perché lo standard OCI rende i pezzi intercambiabili (6.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap06/solution
./run.sh

Esito atteso

  • laricetta.sh genera la ricetta e registra i namespace (TODO 1).
  • run_recipe modifica il config.json (comando + terminale) (TODO 2) ed esegue con runc (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED: la ricetta elenca i namespace della Parte 1, runc la esegue, e cambiando la ricetta cambia l'output.
07Morire con graziaIntermedio

Cosa costruisci

Chiudiamo l'architettura del motore seguendo un container dalla nascita alla morte — e quasi tutte le trappole hanno la stessa radice: quel PID 1 che hai incontrato «a mani nude» nel capitolo 1. In questo laboratorio confronti due container di fronte a docker stop: uno il cui PID 1 ignora SIGTERM, e uno con un vero init al posto giusto. Misuri la differenza — dieci secondi contro un istante — e capisci, con il cronometro alla mano, perché tanti container «ci mettono sempre dieci secondi» a fermarsi.

Obiettivi

  • Osservare la sequenza di docker stop: SIGTERM, attesa (grace period), poi SIGKILL (7.3).
  • Riconoscere la trappola del PID 1: un processo che ignora SIGTERM attende tutto il grace (7.3).
  • Curare la trappola con --init (tini) come PID 1, che inoltra il segnale (7.5).
  • Leggere gli exit code come diagnosi: 137 (SIGKILL) contro 143 (SIGTERM pulito) (7.3).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap07/solution
./run.sh

Esito atteso

  • measure cronometra lo stop con il grace period (TODO 1) e registra l'exit code (TODO 3).
  • Il container B usa --init (TODO 2).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED: A attende il grace ed esce 137, B si ferma subito ed esce 143, e A è nettamente più lento di B.
08La lista di caricoIntermedio

Cosa costruisci

Chiusa l'architettura del motore, comincia l'artigianato: le immagini. E la prima sorpresa è che un'immagine non è un blocco monolitico ma una pila di strati più una lista di carico che li elenca — proprio come una nave non è un guscio pieno alla rinfusa, ma container impilati e un manifesto che dice cosa c'è e in che ordine. In questo laboratorio dissezioni un'immagine a mani nude: ne conti i layer, ne leggi il digest sha256 che la sigilla, e dimostri perché due immagini diverse condividono gli stessi strati senza copiarli.

Obiettivi

  • Vedere che un'immagine è una config più uno stack di layer, e che ogni istruzione che tocca il filesystem aggiunge un layer (8.1, 8.2).
  • Leggere l'image ID come digest sha256 della config: l'immagine è identificata dal suo contenuto, quindi immutabile e verificabile (8.3).
  • Riconoscere i layer come diff content-addressed (ognuno un digest) (8.2).
  • Dimostrare lo sharing: un'immagine costruita sopra un'altra riusa gli stessi layer, senza duplicarli — la base della cache e del pull per digest (8.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap08/solution
./run.sh

Esito atteso

  • lanatomia.sh registra numero di layer dell'immagine e della base (TODO 1).
  • Registra l'image ID e il digest del layer in cima (TODO 2).
  • Costruisce l'immagine figlia e conta i layer condivisi (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
09Il piano di caricoFondamentale

Cosa costruisci

Nel capitolo 8 hai smontato un'immagine nei suoi strati; ora la costruisci tu, con intenzione. Il Dockerfile è il piano di carico della nave: un elenco ordinato di istruzioni che dicono da quale scafo partire, cosa imbarcare, dove metterlo e quale comando eseguire alla partenza. In questo laboratorio completi un Dockerfile con le istruzioni fondamentali — COPY, ENV, CMD — e verifichi che l'immagine costruita si comporti esattamente come l'hai dichiarata: il file al posto giusto, la variabile impostata, il comando di default che parte da solo.

Obiettivi

  • Partire da un'immagine base con FROM e fissare la directory di lavoro con WORKDIR (9.1, 9.4).
  • Portare un file dal contesto di build dentro l'immagine con COPY (9.3).
  • Impostare una variabile d'ambiente con ENV, che l'applicazione legge a runtime (9.4).
  • Dichiarare il comando di default con CMD, e vedere che parte quando avvii il container senza argomenti (9.5).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap09/solution
./run.sh

Esito atteso

  • Il Dockerfile copia greet.sh nella WORKDIR (TODO 1).
  • Imposta la variabile d'ambiente GREETING (TODO 2).
  • Dichiara il comando di default con CMD (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
10Il comandante e gli ordiniIntermedio

Cosa costruisci

Hai dato un comando di default con CMD; ma chi comanda davvero alla partenza? Un container ha un solo processo al posto d'onore — il PID 1 che hai incontrato nel capitolo 7 — e due istruzioni decidono chi è e cosa esegue: ENTRYPOINT e CMD. La metafora è quella del comandante e degli ordini: ENTRYPOINT è il comandante fisso della nave, CMD sono gli ordini di default, che si possono cambiare alla partenza. In questo laboratorio li combini, vedi come gli argomenti passati a docker run sovrascrivono CMD ma non ENTRYPOINT, e verifichi che la forma esatta con cui li scrivi decide se il tuo processo è PID 1 o finisce avvolto in una shell.

Obiettivi

  • Distinguere ENTRYPOINT (l'eseguibile fisso) da CMD (gli argomenti di default) e vederli combinati (10.2, 10.3, 10.5).
  • Osservare che gli argomenti passati a docker run sovrascrivono CMD ma lasciano intatto ENTRYPOINT (10.5).
  • Capire la forma exec contro la forma shell: la exec rende il tuo processo PID 1 (10.1, 10.4).
  • Ricollegare il PID 1 ai segnali del capitolo 7: chi è PID 1 riceve SIGTERM.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap10/solution
./run.sh

Esito atteso

  • Il Dockerfile copia entry.sh nell'immagine (TODO 1).
  • Dichiara ENTRYPOINT in forma exec (TODO 2).
  • Dà argomenti di default con CMD (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
11Il magazzino e la nave leggeraIntermedio

Cosa costruisci

Sai costruire un'immagine; ora impari a costruirla veloce e a spedirla prima è la cache: Docker riusa uno strato finché l'istruzione che lo produce e tutto ciò che sta sotto non cambiano — quindi l'ordine delle istruzioni decide quanto lavoro rifai a ogni build. La seconda è il Multi-Stage: usi uno stage come magazzino dove assembli con tutti gli attrezzi, e poi imbarchi sulla nave finale — leggera — solo la merce finita. In questo laboratorio le combini: metti ciò che cambia raramente prima di ciò che cambia spesso, e spedisci solo l'artefatto.

Obiettivi

  • Capire come la cache riusa gli strati e perché l'ordine delle istruzioni conta (11.1, 11.2).
  • Usare un Multi-Stage Build: uno stage di build nominato e uno stage finale (11.3).
  • Copiare dallo stage di build solo l'artefatto con COPY --from (11.4).
  • Ottenere un'immagine finale leggera, priva degli attrezzi di build (11.5).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap11/solution
./run.sh

Esito atteso

  • Lo stage di build è nominato con AS (TODO 1).
  • Lo stage finale copia solo l'artefatto con COPY --from (TODO 2).
  • Le dipendenze sono copiate e «installate» prima della sorgente (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
12La nave in produzioneAvanzato

Cosa costruisci

Un'immagine che funziona sulla tua macchina non è ancora un'immagine da mettere in mare. La Parte 3 si chiude portando l'idea fino in fondo: in produzione la nave deve essere leggera e ben sorvegliata — solo l'equipaggio necessario, niente chiavi di troppo. La chiave di troppo, quasi sempre, è root: per default un container gira come root, e un processo compromesso che è root dentro il container è molto più pericoloso di uno che non lo è. In questo laboratorio costruisci un'immagine di produzione che gira come utente non privilegiato, proprietario solo di ciò che gli serve — e verifichi, permesso alla mano, che non può scrivere dove non deve.

Obiettivi

  • Creare un utente non-root dedicato e farci girare l'app (12.2).
  • Dare all'utente la proprietà della sola directory dell'app: privilegio minimo (12.3).
  • Dichiarare l'utente nell'immagine con USER, così vale per ogni container (12.2).
  • Vedere la differenza di rischio tra root e non-root nel container (12.1).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap12/solution
./run.sh

Esito atteso

  • Il Dockerfile crea un utente non-root (TODO 1).
  • Assegna a quell'utente la proprietà della directory dell'app (TODO 2).
  • Dichiara USER per girare non-root (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
13Ciò che resta a terraFondamentale

Cosa costruisci

Un container è una nave: parte, fa il suo viaggio, e prima o poi viene demolita. Tutto ciò che scrivi nella sua stiva — lo strato scrivibile che hai conosciuto nel capitolo 8 — va giù con lei quando la rimuovi. È la sorpresa che coglie tutti la prima volta: fai girare un database, lo popoli, rimuovi il container per aggiornarlo e i dati non ci sono più. La Parte 4 risponde a questa domanda — se il container è effimero, dove vivono i dati? — e la risposta è: a terra. In questo laboratorio vedi con mano che lo strato del container muore con lui, e che un volume, custodito dal demone a terra, sopravvive alla nave.

Obiettivi

  • Vedere che lo strato scrivibile del container è effimero: un container nuovo non vede i file scritti dal precedente (13.1, 13.2).
  • Creare un volume con nome e scriverci dentro (13.3).
  • Verificare che il volume sopravvive alla rimozione del container che l'ha scritto (13.3).
  • Capire che il volume è un oggetto di prima classe, con un ciclo di vita proprio, indipendente da qualsiasi container (13.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap13/solution
./run.sh

Esito atteso

  • aterra.sh crea il volume con nome (TODO 1).
  • Scrive un file nel volume da un container usa-e-getta (TODO 2).
  • Rilegge il file da un container nuovo che monta lo stesso volume (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
14Tre modi di stivareIntermedio

Cosa costruisci

Nel capitolo 13 hai visto che i dati vanno tenuti a terra, non nella stiva del container. Ma «a terra» ha tre indirizzi diversi, e sceglierli male costa caro. Il volume è il magazzino del porto: lo gestisce il demone, è portabile e fatto per i dati. Il bind mount è un molo condiviso con l'host: monti una cartella della tua macchina dentro il container, e ciò che scrivi si vede da entrambe le parti — comodo in sviluppo, delicato coi permessi. Il tmpfs è l'armadietto veloce di bordo: vive in memoria, non tocca il disco, e si svuota all'arrivo. In questo laboratorio li usi tutti e tre e ne verifichi il tratto che li distingue.

Obiettivi

  • Usare un bind mount: una cartella dell'host montata dentro, con scrittura bidirezionale (14.2).
  • Usare un volume gestito dal demone, persistente tra i container (14.1).
  • Usare un tmpfs: montaggio in memoria, non persistito e mai su disco (14.3).
  • Riconoscere quale scegliere e perché (14.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap14/solution
./run.sh

Esito atteso

  • imontaggi.sh scrive tramite un bind mount su una cartella dell'host (TODO 1).
  • Rilegge da un container nuovo un file scritto in un volume (TODO 2).
  • Monta un tmpfs e ne riporta il tipo (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
15Il numero sul badgeAvanzato

Cosa costruisci

Hai imparato a girare come utente non-root (capitolo 12) e a montare dati condivisi (capitolo 14). Metti insieme le due cose e inciampi nel classico: il container non-root prova a scrivere nel volume e si sente rispondere «permesso negato». La ragione è che sul confine di un mount i permessi non si leggono per nome ma per numero: conta l'UID: un badge numerico. Se il numero del container non possiede i file montati, non scrive — punto. In questo laboratorio riproduci il mismatch, lo risolvi facendo girare il container con l'UID giusto, e verifichi che il numero attraversa il confine tale e quale: l'UID N dentro è l'UID N sull'host.

Obiettivi

  • Vedere che su un mount condiviso i permessi valgono per UID/GID numerico, non per nome utente (15.1).
  • Riprodurre il problema: un container con UID che non possiede la cartella non può scrivere (15.2).
  • Risolverlo facendo girare il container con l'UID che possiede i file (--user) (15.3).
  • Verificare che l'UID non viene tradotto: il file creato dal container è di proprietà dello stesso UID sull'host (15.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap15/solution
./run.sh

Esito atteso

  • ipermessi.sh riproduce il mismatch: un UID che non possiede la cartella è respinto (TODO 1).
  • Risolve facendo girare il container con l'UID proprietario (TODO 2).
  • Verifica dall'host la proprietà del file creato (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
16Il cavo e la centralinaAvanzato

Cosa costruisci

Finora ogni container era un'isola di processi e di dati; ora scopri che è anche un'isola di rete. La Parte 5 apre i labirinti del networking, e la prima verità è che «dare la rete a un container» non è magia: Docker usa gli stessi mattoni del kernel Linux. Ogni container riceve il proprio network namespace — uno stack di rete tutto suo, con le sue interfacce, il suo IP, la sua tabella di routing — e viene collegato al mondo da un cavo virtuale, la veth pair: un'estremità dentro il container (eth0), l'altra sull'host, attaccata alla centralina condivisa, il bridge docker0. In questo laboratorio lo verifichi con mano: due container, due stack, due indirizzi, ciascuno col suo cavo.

Obiettivi

  • Vedere che un container ha il proprio network namespace, diverso da quello dell'host (16.1).
  • Riconoscere che ogni container ha il suo eth0 e il suo indirizzo, distinto da quello degli altri (16.4).
  • Capire che eth0 è un'estremità di una veth pair: il suo peer sta dall'altra parte, sull'host (16.2).
  • Collegare il tutto al bridge docker0 come centralina condivisa (16.3).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap16/solution
./run.sh

Esito atteso

  • irete.sh legge il network namespace del container (TODO 1).
  • Legge l'IP di eth0 di entrambi i container (TODO 2).
  • Legge gli indici della veth (ifindex e iflink) (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
17Il centralino privatoAvanzato

Cosa costruisci

Nel capitolo 16 hai visto la centralina condivisa: il bridge docker0, dove ogni container ha un numero (un IP) ma nessun nome. Va bene per capire il meccanismo, male per costruirci sopra: gli IP cambiano a ogni riavvio, e tutti i container finiscono sullo stesso centralino pubblico. La soluzione è aprire un centralino privato — una rete bridge definita da te. Lì Docker aggiunge due cose che cambiano tutto: una rubrica (un DNS integrato, così i container si chiamano per nome) e una linea isolata (i container di una rete non vedono quelli di un'altra). In questo laboratorio confronti i due mondi: sul bridge di default i nomi non funzionano, sul tuo bridge sì, e chi è fuori dalla rete resta fuori.

Obiettivi

  • Vedere che su una rete bridge definita da te i container si risolvono per nome (DNS integrato) (17.2).
  • Verificare che sul bridge di default la risoluzione per nome non funziona (17.1).
  • Constatare l'isolamento: chi non è sulla rete non raggiunge i suoi container, neppure per IP (17.3).
  • Capire perché una rete per-applicazione è la scelta giusta (17.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap17/solution
./run.sh

Esito atteso

  • irete.sh verifica la risoluzione per nome sulla rete custom (TODO 1).
  • Verifica che sul bridge di default il nome non si risolve (TODO 2).
  • Verifica che un container fuori dalla rete non raggiunge B, neppure per IP (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
18Attaccato o staccatoAvanzato

Cosa costruisci

Bridge di default, bridge custom: finora ogni container aveva il suo stack di rete, isolato e connesso. Ma non è l'unico modo. Ci sono due estremi, e sceglierli è una decisione di progetto. Da un lato il driver host: il container non ha una rete sua, è attaccato direttamente alla presa dell'host — condivide il suo stack, le sue interfacce, le sue porte. Nessun isolamento, nessun NAT, massima velocità, massima esposizione. Dall'altro il driver none: il container ha il suo namespace ma è staccato — solo loopback, nessun cavo verso il mondo. In questo laboratorio tocchi i tre driver a confronto e vedi cosa cambia: chi condivide lo stack dell'host, chi non ha rete affatto, e il bridge nel mezzo.

Obiettivi

  • Vedere che il driver host fa condividere al container il network namespace dell'host — nessun isolamento (18.1).
  • Vedere che il driver none dà al container un namespace suo ma senza eth0 — nessuna connettività (18.2).
  • Confrontare col bridge di default: namespace proprio e una eth0 — isolato ma connesso (18.4).
  • Capire come si sceglie il driver e perché host è potente ma delicato (18.3).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap18/solution
./run.sh

Esito atteso

  • idriver.sh legge il namespace del container con driver host (TODO 1).
  • Legge namespace ed eth0 del container con driver none (TODO 2).
  • Legge namespace ed eth0 del container con bridge di default (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
19Sulla banchina, e oltre l'orizzonteCloud Architect

Cosa costruisci

Fin qui i container vivevano dietro il centralino: un IP privato, il NAT a fare da filtro tra loro e la rete vera. Va bene per la maggior parte dei casi, ma a volte serve altro: che il container appaia sulla rete fisica come una macchina a sé, con un suo indirizzo e un suo MAC, senza mediazioni. È il driver macvlan — il container sulla banchina, non più dietro il vetro. In questo laboratorio dai a due container un indirizzo diretto sulla rete di un'interfaccia parent e verifichi che ciascuno ha il suo MAC e che si parlano sullo stesso segmento. Poi guardi oltre l'orizzonte del singolo host: ipvlan (la variante che condivide il MAC) e overlay (la rete che attraversa più host), il ponte verso l'orchestrazione.

Obiettivi

  • Dare a un container un indirizzo diretto sulla rete di un parent con macvlan (19.1).
  • Verificare che ogni container ha il proprio MAC — una identità L2 a sé sul segmento (19.1).
  • Verificare che due container macvlan sullo stesso parent si raggiungono a livello 2 (19.1).
  • Inquadrare ipvlan (19.2) e overlay (19.3) e capire quando servono (19.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap19/solution
./run.sh

Esito atteso

  • imacvlan.sh crea la rete macvlan e i due container (TODO 1).
  • Legge il MAC di ciascun container (TODO 2).
  • Verifica la raggiungibilità L2 tra i due (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
20La flotta in un foglioIntermedio

Cosa costruisci

Finora hai comandato una nave alla volta: docker run, docker network, docker volume, un pezzo per volta. Ma un'applicazione vera è una flotta — un web, un database, una cache — e coordinarla a mano, comando su comando, è fragile e irripetibile. La Parte 6 introduce lo strumento che descrive l'intera flotta in un foglio solo: Docker Compose. In un file dichiari i servizi, e Compose fa il resto — crea per te una rete d'applicazione dove i servizi si trovano per nome (come il bridge custom del capitolo 17, ma senza scriverlo), rispetta le dipendenze, e avvia o ferma tutto con un comando. In questo laboratorio progetti un'app a due servizi e verifichi che si parlano per nome e che partono nell'ordine giusto.

Obiettivi

  • Descrivere un'applicazione multi-servizio in un unico file Compose (20.1).
  • Definire due servizi con immagine e comando (20.2).
  • Dichiarare una dipendenza tra servizi con depends_on (20.3).
  • Vedere che Compose dà ai servizi una rete d'app dove si risolvono per nome (20.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap20/solution
./run.sh

Esito atteso

  • compose.yaml definisce db e web con un comando che li tiene in vita (TODO 1, 2).
  • Dichiara che web dipende da db (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
21Il segnale di via liberaAvanzato

Cosa costruisci

Nel capitolo 20 hai fatto partire web dopo db con depends_on. Ma «partito» non è «pronto»: il container di un database può essere avviato mentre il database dentro sta ancora caricando, e web che si connette in quell'istante trova la porta chiusa. depends_on, da solo, aspetta che il container esista, non che il servizio sia pronto ad accettare traffico. Serve un segnale di via libera. In Docker quel segnale è l'healthcheck: il servizio dichiara come si capisce che è davvero pronto, e chi dipende da lui può aspettare quel via libera invece di indovinare con uno sleep. In questo laboratorio dai a db un healthcheck che diventa verde solo dopo un ritardo, e fai aspettare web finché db non è healthy — non solo partito.

Obiettivi

  • Distinguere «partito» (started) da «pronto» (healthy) (21.1).
  • Dichiarare un healthcheck su un servizio: come Docker capisce che è pronto (21.2).
  • Far dipendere web da db con condition: service_healthy — aspettare la prontezza (21.3).
  • Vedere che l'ordine di avvio segue la prontezza, non un tempo arbitrario (21.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap21/solution
./run.sh

Esito atteso

  • db ha un healthcheck (TODO 1) e diventa pronto solo dopo un ritardo (TODO 2).
  • web aspetta che db sia healthy con condition: service_healthy (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
22La cassaforte, non il post-itAvanzato

Cosa costruisci

Un'applicazione non è solo servizi e reti: è anche configurazione. L'indirizzo del database, il livello di log, la chiave dell'API — e, tra questi, cose che non devono finire in chiaro da nessuna parte. Docker Compose offre tre strumenti che è facile confondere. Le variabili d'ambiente configurano il comportamento del servizio. Il file .env tiene i valori fuori dal compose e fuori dal repository. E i secrets sono la cassaforte: dati sensibili montati nel container come file a permessi ristretti, non scritti nell'ambiente dove chiunque ispezioni il container li vedrebbe. In questo laboratorio configuri un servizio con una variabile presa da .env e gli dai una password come secret — e verifichi che il segreto è nel file giusto e non trapela nell'ambiente.

Obiettivi

  • Passare una variabile d'ambiente a un servizio (22.1).
  • Prenderne il valore da un file .env, tenuto fuori dal repository (22.2).
  • Dare un dato sensibile come secret, montato come file in /run/secrets (22.3).
  • Vedere perché un secret non finisce nell'ambiente, a differenza di una env var (22.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap22/solution
./run.sh

Esito atteso

  • app riceve APP_ENV con valore preso da .env (TODO 1).
  • Il secret db_password è definito da un file (TODO 2) e assegnato ad app (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
23Re nella propria stanzaCloud Architect

Cosa costruisci

Apriamo la Parte 7 — sicurezza e day-2 — dalla domanda che sta sotto a tutto: chi è root, davvero? Nel Docker classico il demone gira come root sull'host, e chi può parlargli (il gruppo docker) è root a tutti gli effetti. Un container che gira come root è root sull'host per i file che monta, e un'evasione è un'evasione da root. La modalità rootless ribalta il quadro usando lo USER namespace del capitolo 2: il demone e i container girano dentro un namespace dove sei «root», ma quel root è mappato a un utente non privilegiato sull'host. Sei re nella tua stanza, un utente qualunque fuori. In questo laboratorio tocchi con mano la mappatura: dentro sei uid 0 con tutte le capability, fuori sei il tuo utente, e quel «root» non può fare nulla di privilegiato sull'host.

Obiettivi

  • Entrare in uno USER namespace che ti mappa a root e vedere che dentro sei uid 0 (23.2).
  • Verificare che quel root è mappato al tuo utente reale, non privilegiato, sull' host (23.3).
  • Constatare che quel «root» non può toccare i file di root dell'host — è potente solo dentro il namespace (23.3).
  • Capire perché questo modello riduce il raggio d'azione di un'evasione (23.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap23/solution
./run.sh

Esito atteso

  • irootless.sh legge l'uid dentro lo user namespace (TODO 1).
  • Legge il proprietario, sull'host, di un file creato «da root» dentro (TODO 2).
  • Verifica se quel «root» può scrivere in /etc dell'host (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
24Le chiavi giuste, non tutteCloud Architect

Cosa costruisci

Nel capitolo 23 hai visto chi è root; ora vedi che root non è un blocco unico. I poteri di root sono spezzati dal kernel in tante chiavi separate — le capabilities: il permesso di aprire socket raw, di legare porte basse, di montare filesystem, di cambiare proprietari. Un container non ha bisogno quasi mai di tutte. Il principio è lo stesso di una cassaforte ben fatta: dai a ciascuno solo la chiave che gli serve. E sopra le capabilities ci sono altri due strati — seccomp, che filtra le syscall, e AppArmor o SELinux, che confinano cosa un processo può toccare. Difesa in profondità. In questo laboratorio tocchi le capabilities con mano: togli tutto, e la stessa operazione fallisce; ridai la chiave giusta, e riprende — senza restituire tutte le altre.

Obiettivi

  • Vedere che root non è monolitico: i suoi poteri sono capabilities separate (24.1).
  • Togliere tutte le capabilities con --cap-drop ALL e vedere un'operazione fallire (24.2).
  • Ridare solo la capability necessaria con --cap-add: privilegio minimo (24.2).
  • Inquadrare seccomp (24.3) e AppArmor/SELinux (24.4) come strati aggiuntivi.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap24/solution
./run.sh

Esito atteso

  • icapabilities.sh prova il ping con le capabilities di default (TODO 1).
  • Lo riprova con --cap-drop ALL (TODO 2).
  • Lo riprova con --cap-drop ALL --cap-add NET_RAW (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
25Il diario di bordo e i quadrantiAvanzato

Cosa costruisci

La sicurezza previene; l'osservabilità fa vedere. Quando un servizio in produzione si comporta male, la prima domanda è sempre la stessa: cosa sta facendo? Docker risponde con due strumenti. Il diario di bordo sono i log: tutto ciò che il container scrive su standard output e standard error viene catturato dal demone e reso disponibile con docker logs — anche a posteriori, anche dopo che il processo è morto. I quadranti sono le metriche: docker stats mostra in tempo reale CPU, memoria, rete di ogni container. In questo laboratorio leggi il diario di un container — sia stdout sia stderr — scopri dove Docker lo conserva (il logging driver) e leggi i suoi consumi dal vivo.

Obiettivi

  • Recuperare con docker logs ciò che un container scrive su stdout e stderr (25.1).
  • Riconoscere il logging driver che conserva quei log — il json-file di default (25.2).
  • Leggere le metriche dal vivo di un container con docker stats (25.3).
  • Inquadrare l'osservabilità in produzione (25.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap25/solution
./run.sh

Esito atteso

  • iobs.sh legge i log del container (stdout e stderr) (TODO 1).
  • Legge il logging driver (TODO 2).
  • Legge l'uso di memoria con docker stats (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
26La scatola nera del container mutoCloud Architect

Cosa costruisci

Prima o poi arriva il container che non parte, non dice niente, e magari continua a ripartire da solo. I log sono vuoti — muto — e l'istinto è arrendersi. Ma un container non è mai davvero muto: anche quando non scrive una riga, lascia una scatola nera. docker inspect racconta com'è morto — l'exit code, che come hai visto nel capitolo 7 è già una diagnosi — e quante volte è ripartito prima di arrendersi, il segno del crash loop. In questo laboratorio prendi un container che crasha in silenzio, con una restart policy che lo fa ripartire, e ne ricostruisci la storia senza una sola riga di log: dall'exit code e dal contatore dei riavvii.

Obiettivi

  • Riconoscere un container «muto»: i log sono vuoti, non c'è nulla da leggere lì (26.1).
  • Leggere la scatola nera con docker inspect: l'exit code, la vera diagnosi (26.2, 26.4).
  • Riconoscere il crash loop dal contatore dei riavvii e dallo stato finale (26.3).
  • Collegare l'exit code alle sue cause (capitolo 7): 42, 137, 143, 127... (26.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap26/solution
./run.sh

Esito atteso

  • idiag.sh legge i log del container (vuoti) (TODO 1).
  • Legge l'exit code da docker inspect (TODO 2).
  • Legge il contatore dei riavvii e lo stato finale (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.
27Pulire la stiva, guardare il mareCloud Architect

Cosa costruisci

Ogni viaggio lascia dei residui. Container fermati e mai rimossi, immagini vecchie che nessuno usa più, volumi rimasti orfani quando il loro container è sparito: col tempo la stiva si riempie e il disco si esaurisce. Il day-2 — la vita dopo il primo deploy — è fatto anche di questo: sapere cosa occupa spazio e recuperarlo, ma con giudizio. Perché su una macchina condivisa un docker system prune dato alla leggera cancella anche il lavoro degli altri. In questo laboratorio pulisci in sicurezza — solo le risorse che porti l'etichetta tua — e poi alzi lo sguardo: dove finisce Docker su un host solo, e dove comincia l'orizzonte dell'orchestrazione.

Obiettivi

  • Riconoscere gli orfani: container fermi, volumi inutilizzati che occupano spazio (27.1).
  • Recuperare spazio in sicurezza, con ambito ristretto (label, nomi), mai un prune globale su un host condiviso (27.2).
  • Verificare che solo le tue risorse sono state rimosse (27.2).
  • Inquadrare gli orizzonti: i limiti del singolo host e il ponte all'orchestrazione (27.4).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd docker/ed1/cap27/solution
./run.sh

Esito atteso

  • imaint.sh recupera i propri container fermi con un prune filtrato per etichetta (TODO 1).
  • Rimuove il proprio volume con nome (TODO 2).
  • Riconta e conferma che nulla di suo resta (TODO 3).
  • run.sh stampa OK 1..3 e ALL CHECKS PASSED.