<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Docker - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Impara a programmare gratuitamente! Tutorial di programmazione su Python, JavaScript, Linux e molto altro. ]]>
        </description>
        <link>https://www.freecodecamp.org/italian/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Docker - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/italian/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 14 May 2026 19:58:22 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/italian/news/tag/docker/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Il Manuale Docker – Docker per Principianti ]]>
                </title>
                <description>
                    <![CDATA[ Il concetto di containerizzazione è piuttosto risalente, ma la nascita del  Docker Engine [https://docs.docker.com/get-started/overview/#docker-engine] nel 2013 ha reso molto più semplice la sua applicazione. Secondo un sondaggio del 2020 di Stack Overflow [https://insights.stackoverflow.com/survey/2020#overview], Docker [https://docker.com/] è la 1ᵃ piattaforma più richiesta [https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-wanted5] , la 2ᵃ piat ]]>
                </description>
                <link>https://www.freecodecamp.org/italian/news/il-manuale-docker/</link>
                <guid isPermaLink="false">6389ff6004f75a068657d2fa</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dario Di Cillo ]]>
                </dc:creator>
                <pubDate>Fri, 03 Feb 2023 05:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/italian/news/content/images/2022/12/docker-1280x612-2021.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Articolo originale:</strong> <a href="https://www.freecodecamp.org/news/the-docker-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">The Docker Handbook – Learn Docker for Beginners</a>
      </p><p>Il concetto di containerizzazione è piuttosto risalente, ma la nascita del <a href="https://docs.docker.com/get-started/overview/#docker-engine">Docker Engine</a> nel 2013 ha reso molto più semplice la sua applicazione.</p><p>Secondo un <a href="https://insights.stackoverflow.com/survey/2020#overview">sondaggio del 2020 di Stack Overflow</a>, <a href="https://docker.com/" rel="noopener noreferrer">Docker</a> è la <a href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-wanted5">1ᵃ piattaforma più richiesta</a>, la <a href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-loved5">2ᵃ piattaforma più amata</a> e la <a href="https://insights.stackoverflow.com/survey/2020#technology-platforms">3ᵃ piattaforma più popolare</a>.</p><p>Per quanto possa essere richiesta, i primi passi possono essere complicati. In questo manuale, impareremo tutto dalle basi della containerizzazione fino a un livello più intermedio. Dopo aver letto l'intero manuale, dovresti essere in grado di:</p><ul><li>Containerizzare (quasi) qualsiasi applicazione</li><li>Caricare immagini Docker personalizzate su registri online</li><li>Lavorare con container multipli usando Docker Compose</li></ul><h2 id="prerequisiti"><strong>Prerequisiti</strong></h2><ul><li>Familiarità con il terminale Linux</li><li>Familiarità con JavaScript (alcuni progetti richiedono l'uso di JavaScript)</li></ul><h2 id="sommario"><strong>Sommario</strong></h2><!--kg-card-begin: markdown--><ul>
<li><a href="#introduzione-alla-containerizzazione-e-a-docker">Introduzione alla containerizzazione e a Docker</a></li>
<li><a href="#come-installare-docker">Come installare Docker</a>
<ul>
<li><a href="#come-installare-docker-su-macos">Come installare Docker su macOS</a></li>
<li><a href="#come-installare-docker-su-windows">Come installare Docker su Windows</a></li>
<li><a href="#come-installare-docker-su-linux">Come installare Docker su Linux</a></li>
</ul>
</li>
<li><a href="#hello-world-in-docker-introduzione-a-docker">Hello World in Docker - Introduzione a Docker</a>
<ul>
<li><a href="#cos-un-container">Cos'è un container</a></li>
<li><a href="#cos-un-immagine-docker">Cos'è un'immagine Docker</a></li>
<li><a href="#cos-un-registro-docker">Cos'è un registro Docker</a></li>
<li><a href="#una-panoramica-dell-architettura-docker">Una panoramica dell'architettura Docker</a></li>
<li><a href="#il-quadro-completo">Il quadro completo</a></li>
</ul>
</li>
<li><a href="#le-basi-della-manipolazione-dei-container-in-docker">Le basi della manipolazione dei container in Docker</a>
<ul>
<li><a href="#come-eseguire-un-container">Come eseguire un container</a></li>
<li><a href="#come-pubblicare-una-porta">Come pubblicare una porta</a></li>
<li><a href="#come-usare-l-opzione-detach">Come usare l'opzione detach</a></li>
<li><a href="#come-elencare-i-container">Come elencare i container</a></li>
<li><a href="#come-dare-un-nome-a-un-container-o-rinominarlo">Come dare un nome a un container o rinominarlo</a></li>
<li><a href="#come-fermare-un-container-in-esecuzione">Come fermare un container in esecuzione</a></li>
<li><a href="#come-riavviare-un-container">Come riavviare un container</a></li>
<li><a href="#come-creare-un-container-senza-eseguirlo">Come creare un container senza eseguirlo</a></li>
<li><a href="#come-rimuovere-dei-container-sospesi">Come rimuovere dei container sospesi</a></li>
<li><a href="#come-eseguire-un-container-in-modalit-interattiva">Come eseguire un container in modalità interattiva</a></li>
<li><a href="#come-eseguire-comandi-all-interno-di-un-container">Come eseguire comandi all'interno di un container</a></li>
<li><a href="#come-lavorare-con-immagini-eseguibili">Come lavorare con immagini eseguibili</a></li>
</ul>
</li>
<li><a href="#manipolazione-base-delle-immagini-docker">Manipolazione base delle immagini Docker</a>
<ul>
<li><a href="#come-creare-un-immagine-docker">Come creare un'immagine Docker</a></li>
<li><a href="#come-taggare-le-immagini-docker">Come taggare le immagini Docker</a></li>
<li><a href="#come-elencare-e-rimuovere-immagini-docker">Come elencare e rimuovere immagini Docker</a></li>
<li><a href="#come-comprendere-i-livelli-di-un-immagine-docker">Come comprendere i livelli di un'immagine Docker</a></li>
<li><a href="#come-eseguire-il-build-di-nginx-dalla-sorgente">Come eseguire il build di NGINX dalla sorgente</a></li>
<li><a href="#come-ottimizzare-le-immagini-docker">Come ottimizzare le immagini Docker</a></li>
<li><a href="#alpine-linux">Alpine Linux</a></li>
<li><a href="#come-creare-immagini-docker-eseguibili">Come creare immagini Docker eseguibili</a></li>
<li><a href="#come-condividere-le-tue-immagini-docker-online">Come condividere le tue immagini Docker online</a></li>
</ul>
</li>
<li><a href="#come-containerizzare-una-app-javascript">Come containerizzare una app JavaScript</a>
<ul>
<li><a href="#come-scrivere-il-dockerfile-di-sviluppo">Come scrivere il Dockerfile di sviluppo</a></li>
<li><a href="#come-lavorare-con-i-bind-mount-in-docker">Come lavorare con i bind mount in Docker</a></li>
<li><a href="#come-lavorare-con-volumi-anonimi-in-docker">Come lavorare con i volumi anonimi in Docker</a></li>
<li><a href="#come-eseguire-dei-build-multi-stadio-in-docker">Come eseguire dei build multi-stadio in Docker</a></li>
<li><a href="#come-ignorare-i-file-non-necessari">Come ignorare i file non necessari</a></li>
</ul>
</li>
<li><a href="#manipolazione-base-delle-reti-in-docker">Manipolazione base delle reti in Docker</a>
<ul>
<li><a href="#basi-delle-reti-docker">Basi delle reti in Docker</a></li>
<li><a href="#come-creare-un-bridge-in-docker">Come creare un bridge in Docker</a></li>
<li><a href="#come-collegare-un-container-a-una-rete-in-docker">Come collegare  un container a una rete in Docker</a></li>
<li><a href="#come-scollegare-dei-container-da-una-rete-in-docker">Come scollegare dei container da una rete in Docker</a></li>
<li><a href="#come-rimuovere-una-rete-in-docker">Come rimuovere una rete in Docker</a></li>
</ul>
</li>
<li><a href="#come-containerizzare-un-applicazione-javascript-multi-container">Come containerizzare un'applicazione JavaScript multi-container</a>
<ul>
<li><a href="#come-eseguire-il-server-del-database">Come eseguire il server del database</a></li>
<li><a href="#come-lavorare-con-i-volumi-in-docker">Come lavorare con i volumi in Docker</a></li>
<li><a href="#come-accedere-ai-log-da-un-container-in-docker">Come accedere ai log da un container in Docker</a></li>
<li><a href="#come-creare-una-rete-e-collegare-il-server-del-database-in-docker">Come creare una rete e collegare il server del database in Docker</a></li>
<li><a href="#come-scrivere-il-dockerfile">Come scrivere il Dockerfile</a></li>
<li><a href="#come-eseguire-i-comandi-in-un-container-in-esecuzione">Come eseguire i comandi in un container in esecuzione</a></li>
<li><a href="#come-scrivere-degli-script-di-gestione-in-docker">Come scrivere degli script di gestione in  Docker</a></li>
</ul>
</li>
<li><a href="#come-comporre-i-progetti-usando-docker-compose">Come comporre i progetti usando Docker Compose</a>
<ul>
<li><a href="#le-basi-di-docker-compose">Le basi di Docker Compose</a></li>
<li><a href="#come-avviare-i-servizi-in-docker-compose">Come avviare i servizi in Docker Compose</a></li>
<li><a href="#come-elencare-i-servizi-in-docker-compose">Come elencare i servizi in Docker Compose</a></li>
<li><a href="#come-eseguire-dei-comandi-all-interno-di-un-servizio-in-esecuzione-in-docker-compose">Come eseguire dei comandi all'interno di un servizio in esecuzione in Docker Compose</a></li>
<li><a href="#come-accedere-ai-log-da-un-servizio-in-esecuzione-in-docker-compose">Come accedere ai log da un servizio in esecuzione in Docker Compose</a></li>
<li><a href="#come-fermare-dei-servizi-in-docker-compose">Come fermare dei servizi in Docker Compose</a></li>
<li><a href="#come-comporre-un-applicazione-full-stack-in-docker-compose">Come comporre un'applicazione full-stack in Docker Compose</a></li>
</ul>
</li>
<li><a href="#conclusione">Conclusione</a></li>
</ul>
<!--kg-card-end: markdown--><h2 id="il-codice-dei-progetti"><strong>Il codice dei progetti</strong></h2><p>Il codice dei progetti di esempio è disponibile nel seguente repository:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/fhsinchy/docker-handbook-projects/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - fhsinchy/docker-handbook-projects: Project codes used in “The Docker Handbook”</div><div class="kg-bookmark-description">Project codes used in “The Docker Handbook”. Contribute to fhsinchy/docker-handbook-projects development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" width="32" height="32" alt="favicon" loading="lazy"><span class="kg-bookmark-author">fhsinchy</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://repository-images.githubusercontent.com/277878515/d76adb00-c391-11ea-8ecb-718db51373b2" width="1280" height="640" alt="d76adb00-c391-11ea-8ecb-718db51373b2" loading="lazy"></div></a><figcaption>dammi una ⭐ per mantenermi motivato</figcaption></figure><p>Puoi trovare il codice completo nel branch <code>completed</code>.</p><h2 id="contributi"><strong>Contributi</strong></h2><p>Questo libro è completamente open-source e le contribuzioni di qualità sono ben accette. Puoi trovare il contenuto integrale nel seguente repository:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/fhsinchy/the-docker-handbook"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - fhsinchy/the-docker-handbook: An open-source full-length book on Docker</div><div class="kg-bookmark-description">An open-source full-length book on Docker. Contribute to fhsinchy/the-docker-handbook development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" width="32" height="32" alt="favicon" loading="lazy"><span class="kg-bookmark-author">fhsinchy</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/d605cbdcd2a2308bb85581ea9330fce972cef3627b1249518902bf5adc188f5b/fhsinchy/the-docker-handbook" width="1200" height="600" alt="the-docker-handbook" loading="lazy"></div></a><figcaption>dammi una ⭐ per mantenermi motivato</figcaption></figure><p>Di solito, apporto le modifiche prima alla versione GitBook del manuale e poi la pubblico su freeCodeCamp. Puoi trovare la versione sempre aggiornata e spesso instabile del libro al seguente link:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://docker-handbook.farhan.dev/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">The Docker Handbook - The Docker Handbook</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://app.gitbook.com/public/emojis/1f4d9.png?v=6.0.0" width="600" height="400" alt="1f4d9.png?v=6.0" loading="lazy"><span class="kg-bookmark-publisher">The Docker Handbook</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://842847971-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MD_PXBw_anEnk5G-Lck%2F-MD_PhskKcWWHdAgEPMM%2F-MD_QKqWhQ7Ymti30dvJ%2Fdocker-handbook-preview.png?alt=media&amp;token=d26c0839-2592-440d-b8bb-13dfec5e489a" width="1200" height="628" alt="assets%2F-MD_PXBw_anEnk5G-Lck%2F-MD_PhskKcWWHdAgEPMM%2F-MD_QKqWhQ7Ymti30dvJ%2Fdocker-handbook-preview" loading="lazy"></div></a><figcaption>non dimenticare di lasciare una ⭐ sul repository</figcaption></figure><p>Se desideri una versione statica ma stabile, freeCodeCamp è miglior posto dove trovarla:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://www.freecodecamp.org/news/the-docker-handbook/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">The Docker Handbook – Learn Docker for Beginners</div><div class="kg-bookmark-description">The concept of containerization itself is pretty old. But the emergence of the Docker Engine [https://docs.docker.com/get-started/overview/#docker-engine] in2013 has made it much easier to containerize your applications. According to the Stack Overflow Developer Survey - 2020[https://insights.stackoverflow.com/survey/2020#overview…</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn.freecodecamp.org/universal/favicons/favicon.ico" width="48" height="48" alt="favicon" loading="lazy"><span class="kg-bookmark-author">Farhan Hasin Chowdhury</span><span class="kg-bookmark-publisher">freeCodeCamp.org</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.freecodecamp.org/news/content/images/2021/02/docker-1280x612-2021.png" width="600" height="400" alt="docker-1280x612-2021" loading="lazy"></div></a><figcaption>condividi questo manuale</figcaption></figure><p>Qualsiasi versione finirai per leggere, non dimenticarmi di dirmi cosa ne pensi. Le critiche costruttive sono sempre ben accette.</p><h2 id="introduzione-alla-containerizzazione-e-a-docker"><strong>Introduzione alla containerizzazione e a Docker</strong></h2><p>Secondo <a href="https://www.ibm.com/cloud/learn/containerization#toc-what-is-co-r25Smlqq" rel="noopener noreferrer">IBM</a>,</p><blockquote>La containerizzazione implica l'incapsulamento o l'impacchettamento del codice di un software e di tutte le sue dipendenze, in modo che possa essere eseguito in modo uniforme e coerente su qualsiasi infrastruttura.</blockquote><p>In altre parole, la containerizzazione ti consente di impacchettare un software con tutte le sue dipendenze in un pacchetto autonomo, così da poterlo eseguire senza passare per un fastidioso processo di configurazione.‌</p><p>Consideriamo un caso pratico. Immagina di aver sviluppato una fantastica applicazione per la gestione di libri, che può contenere informazioni su tutti i libri che possiedi e può anche servire come sistema di prestito per i tuoi amici.</p><p>Se fai una lista delle dipendenze, dovrebbe essere qualcosa del genere:</p><ul><li>Node.js</li><li>Express.js</li><li>SQLite3</li></ul><p>Bene, teoricamente dovrebbe essere tutto. Ma in pratica ci sono anche altre cose. Viene fuori che <a href="https://nodejs.org/" rel="noopener noreferrer">Node.js</a> usa uno strumento di build chiamato <code>node-gyp</code> per il build degli add-on. E secondo le <a href="https://github.com/nodejs/node-gyp#installation">istruzioni di installazione</a> nel <a href="https://github.com/nodejs/node-gyp">repository ufficiale</a>, questo strumento di build richiede Python 2 o 3 e una appropriata tool-chain per il compilatore C/C++.</p><p>Considerando tutto ciò, la lista finale delle dipendenze è la seguente:</p><ul><li>Node.js</li><li>Express.js</li><li>SQLite3</li><li>Python 2 o 3</li><li>C/C++ tool-chain</li></ul><p>Installare Python 2 o 3 è piuttosto semplice indipendentemente dalla piattaforma, mentre configurare la tool-chain di C/C++ è abbastanza facile su Linux, ma su Windows e Mac è un'attività piuttosto spiacevole.</p><p>Su Windows, il pacchetto degli strumenti di build di C/C++ è nell'ordine dei gigabyte e richiede del tempo per essere installato. Su un Mac, puoi installare l'enorme applicazione <a href="https://developer.apple.com/xcode/" rel="noopener noreferrer">Xcode</a> oppure il pacchetto più piccolo di <a href="https://github.com/nodejs/node-gyp"></a><a href="https://github.com/nodejs/node-gyp"></a><a href="https://developer.apple.com/downloads/">strumenti per la riga di comando per</a><a href="https://developer.apple.com/downloads/" rel="noopener noreferrer"> Xcode</a>.</p><p>Indipendentemente da ciò che installi, potrebbero esserci comunque problemi con gli aggiornamenti del sistema operativo. È così comune che ci sono delle <a href="https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md"></a><a href="https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md">note d'installazione per macOS Catalina</a> disponibili sul repository ufficiale.</p><p>Ipotizziamo che hai superato tutte queste noie, configurando tutte le dipendenze e hai iniziato a lavorare sul progetto. Significa che sei fuori pericolo? Naturalmente no.</p><p>E se un tuo collega utilizza Windows mentre tu usi Linux? Adesso devi considerare tutte le incongruenze relative a come i due sistemi operativi gestiscono i percorsi. Oppure il fatto che tecnologie popolari come <a href="https://nginx.org/" rel="noopener noreferrer">nginx</a> non sono ottimizzate per Windows. Alcune tecnologie come <a href="https://redis.io/" rel="noopener noreferrer">Redis</a> non sono neanche pre-configurate per Windows.</p><p>Anche se dovessi affrontare l'intera fase di sviluppo, cosa accade se la persona responsabile della gestione dei server esegue una procedura di deployment errata?</p><p>Tutti questi problemi possono essere risolti se soltanto potessi:</p><ul><li>Sviluppare ed eseguire l'applicazione all'interno di un ambiente isolato (detto container) che corrisponde all'ambiente finale di distribuzione.</li><li>Mettere l'applicazione all'interno di un singolo file (detto immagine) insieme a tutte le sue dipendenze e le configurazioni necessarie per il deployment.</li><li>E condividere l'immagine a un server centrale (detto registro), accessibile da chiunque abbia l'autorizzazione appropriata.</li></ul><p>I tuoi colleghi saranno in grado di scaricare l'immagine dal registro, eseguire l'applicazione, dato che è all'interno di un ambiente isolato e libero dalle incongruenze dovute a una specifica piattaforma, o anche distribuire direttamente da un server, visto che l'immagine è provvista di tutte le configurazioni di produzione appropriate.</p><p>Questa è l'idea alla base della containerizzazione: inserire un'applicazione all'interno di un pacchetto autonomo, renderla portabile e riproducibile in diversi ambienti.</p><p><strong>E adesso la domande è "che ruolo svolge Docker?".</strong></p><p>Come già menzionato, la containerizzazione è un'idea che risolve una miriade di problemi nello sviluppo di software inserendo le cose in una scatola.</p><p>Questa idea può essere messa in atto in vari modi. <a href="https://www.docker.com/">Docker</a> è uno di questi. È una piattaforma open-source di containerizzazione che ti consente di containerizzare le tue applicazioni, condividerle usando dei registri pubblici o privati, e anche di <a href="https://docs.docker.com/get-started/orchestration/">orchestrarle</a>.</p><p>Docker non è l'unico strumento di containerizzazione sul mercato, è soltanto il più conosciuto. Un altro motore di containerizzazione che amo si chiama <a href="https://podman.io/" rel="noopener noreferrer">Podman</a>, sviluppato da Red Hat. Altri strumenti come <a href="https://github.com/GoogleContainerTools/kaniko" rel="noopener noreferrer">Kaniko</a> di Google, <a href="https://coreos.com/rkt/" rel="noopener noreferrer">rkt</a> di CoreOS sono fantastici, eppure non sono ancora pronti rimpiazzare Docker.</p><p>Se vuoi una lezione di storia, potresti leggere <a href="https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016" rel="noopener noreferrer">A Brief History of Containers: From the 1970s Till Now</a> (risorsa in inglese) che parla dei momenti decisivi di questa tecnologia.</p><h2 id="come-installare-docker"><strong>Come installare Docker</strong></h2><p>L'installazione di Docker varia largamente a seconda del sistema operativo in uso. Ma è comunque generalmente semplice.</p><p>Docker gira alla perfezione su tutte e tre le piattaforme principali, Mac, Windows e Linux. Tra le tre, il processo di installazione su Mac è più semplice, quindi partiremo da qui.</p><h3 id="come-installare-docker-su-macos"><strong>Come installare Docker su macOS</strong></h3><p>Su un mac, tutto ciò che devi fare è andare sulla <a href="https://www.docker.com/products/docker-desktop">pagina ufficiale di download</a> e cliccare il pulsante <em><em>Download for Mac (stable)</em></em>.</p><p>Otterrai un file <em><em>Apple Disk Image</em></em> dall'aspetto normale e al suo interno ci sarà l'applicazione. Tutto ciò che devi fare è trascinare il file e rilasciarlo nella cartella Applicazioni.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/01/drag-docker-in-applications-directory.png" class="kg-image" alt="drag-docker-in-applications-directory" width="600" height="400" loading="lazy"></figure><p>Puoi avviare Docker facendo semplicemente doppio click sull'icona dell'applicazione. Una volta che l'applicazione si avvia, vedrai l'icona di Docker apparire nella barra del menu.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-menubar.png" class="kg-image" alt="docker-icon-in-menubar" width="600" height="400" loading="lazy"></figure><p>Adesso, apri il terminale ed esegui <code>docker --version</code> e <code>docker-compose --version</code> per assicurarti che l'installazione sia andata a buon fine.</p><h3 id="come-installare-docker-su-windows"><strong>Come installare Docker su Windows</strong></h3><p>Su Windows, la procedura è quasi la stessa, eccetto che ci sono alcuni passaggi aggiuntivi da svolgere. I passaggi per l'installazione sono i seguenti:</p><ol><li>Vai su <a href="https://learn.microsoft.com/it-it/windows/wsl/install">questo sito</a> e segui le istruzioni per installare WSL2 su Windows 10.</li><li>Vai sulla <a href="https://www.docker.com/products/docker-desktop">pagina ufficiale di download</a> e clicca sul pulsante <em><em>Download for Windows (stable)</em></em>.</li><li>Fai doppio click sull'installer scaricato e segui l'installazione predefinita.</li></ol><p>Una volta terminata, avvia <em><em>Docker Desktop</em></em> dal menu start o dal desktop. L'icona di Docker apparirà nella barra delle applicazioni.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-taskbar.png" class="kg-image" alt="docker-icon-in-taskbar" width="600" height="400" loading="lazy"></figure><p>Apri Ubuntu o qualsiasi distribuzione hai installato dal Microsoft Store. Esegui i comandi <code>docker --version</code> e <code>docker-compose --version</code> per assicurarti che l'installazione abbia avuto successo.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-windows.png" class="kg-image" alt="docker-and-compose-version-on-windows" width="600" height="400" loading="lazy"></figure><p>Puoi accedere a Docker anche dal prompt dei comandi o da PowerShell. È solo che preferisco utilizzare WSL2 rispetto ad altre righe di comando su Windows.</p><h3 id="come-installare-docker-su-linux"><strong>Come installare Docker su Linux</strong></h3><p>L'installazione di Docker su Linux è un processo leggermente diverso e, in base alla distribuzione, può cambiare ancora di più. Ma, onestamente, l'installazione è comunque semplice come sulle altre due piattaforme (se non di più).</p><p>Il pacchetto Docker Desktop su Windows o Mac è un insieme di strumenti come <code>Docker Engine</code>, <code>Docker Compose</code>, <code>Docker Dashboard</code>, <code>Kubernetes</code> e qualche altra chicca.</p><p>Su Linux invece, non avrai questa collezione, ma dovrai installare manualmente tutti gli strumenti necessari. Le procedure di installazione per le diverse distribuzioni sono le seguenti:</p><ul><li>Se sei su Ubuntu, puoi seguire la sezione <a href="https://docs.docker.com/engine/install/ubuntu/">Install Docker Engine on Ubuntu</a> della documentazione ufficiale.</li><li>Per le altre distribuzioni, sono disponibili guide nella documentazione ufficiale.</li><li><a href="https://docs.docker.com/engine/install/debian/">Install Docker Engine on Debian</a></li><li><a href="https://docs.docker.com/engine/install/fedora/">Install Docker Engine on Fedora</a></li><li><a href="https://docs.docker.com/engine/install/centos/">Install Docker Engine on CentOS</a></li><li>Se sei su una distribuzione non elencata nella documentazione, puoi seguire la guida <a href="https://docs.docker.com/engine/install/binaries/">Install Docker Engine from binaries</a>.</li><li>Indipendentemente dalla procedura che segui, dovrai svolgere degli importanti <a href="https://docs.docker.com/engine/install/linux-postinstall/">passaggi post-installazione per Linux</a>.</li><li>Una volta terminata l'installazione, dovrai installare un altro strumento chiamato Docker Compose. Puoi seguire la guida <a href="https://docs.docker.com/compose/install/">Install Docker Compose</a> dalla documentazione ufficiale.</li></ul><p>Quando hai concluso l'installazione, apri il terminale ed esegui <code>docker --version</code> e <code>docker-compose --version</code> per assicurarti che l'installazione sia avvenuta con successo.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-linux.png" class="kg-image" alt="docker-and-compose-version-on-linux" width="600" height="400" loading="lazy"></figure><p>Sebbene le prestazioni di Docker siano piuttosto buone indipendentemente dalla piattaforma, preferisco Linux rispetto alle altre. In tutto il manuale, mi alternerò tra le mie postazioni <a href="https://releases.ubuntu.com/20.10/" rel="noopener noreferrer">Ubuntu 20.10</a> e <a href="https://fedoramagazine.org/announcing-fedora-33/" rel="noopener noreferrer">Fedora 33</a>.</p><p>Un'altra cosa che vorrei chiarire fin dall'inizio è che non utilizzerò nessuno strumento GUI per lavorare con Docker in questo manuale.</p><p>Sono consapevole che ci sono disponibili dei validi strumenti GUI per varie piattaforme, ma imparare i comandi docker comuni è uno degli obiettivi primari di questo libro.</p><h2 id="hello-world-in-docker-introduzione-a-docker"><strong>Hello World in Docker – Introduzione a Docker</strong></h2><p>Ora che hai Docker sulla tua macchina, è tempo per eseguire il tuo primo container. Apri il terminale ed esegui il seguente comando:</p><pre><code>docker run hello-world

# Unable to find image 'hello-world:latest' locally
# latest: Pulling from library/hello-world
# 0e03bdcc26d7: Pull complete 
# Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
# Status: Downloaded newer image for hello-world:latest
# 
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# 
# To generate this message, Docker took the following steps:
#  1. The Docker client contacted the Docker daemon.
#  2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
#     (amd64)
#  3. The Docker daemon created a new container from that image which runs the
#     executable that produces the output you are currently reading.
#  4. The Docker daemon streamed that output to the Docker client, which sent it
#     to your terminal.
#
# To try something more ambitious, you can run an Ubuntu container with:
#  $ docker run -it ubuntu bash
# 
# Share images, automate workflows, and more with a free Docker ID:
#  https://hub.docker.com/
#
# For more examples and ideas, visit:
#  https://docs.docker.com/get-started/
</code></pre><p>L'immagine <a href="https://hub.docker.com/_/hello-world" rel="noopener noreferrer">hello-world</a> è un esempio minimale di containerizzazione con Docker. Contiene un singolo programma compilato da un file <a href="https://github.com/docker-library/hello-world/blob/master/hello.c" rel="noopener noreferrer">hello.c</a>, responsabile di stampare sul terminale il messaggio che stai vedendo.</p><p>Nel terminale, puoi usare il comando <code>docker ps -a</code> per dare un'occhiata a tutti i container attualmente in esecuzione o eseguiti in passato:</p><pre><code>docker ps -a

# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
# 128ec8ceab71        hello-world         "/hello"            14 seconds ago      Exited (0) 13 seconds ago                      exciting_chebyshev</code></pre><p>Nell'output, un container chiamato <code>exciting_chebyshev</code> è stato eseguito con il container id <code>128ec8ceab71</code> usando l'immagine <code>hello-world</code>. In <code>Exited (0) 13 seconds ago</code>, il codice di uscita <code>(0)</code> sta a indicare che non sono stati prodotti errori durante il runtime del container.</p><p>Per capire cosa è accaduto dietro le quinte, dovrai acquisire familiarità con l'architettura di Docker e tre dei concetti fondamentali della containerizzazione in generale, che sono:</p><ul><li>Container</li><li>Immagine</li><li>Registro</li></ul><p>Li ho elencati in ordine alfabetico e inizierò le mie spiegazioni dal primo della lista.</p><h3 id="cos-un-container"><strong>Cos'è un container?</strong></h3><p>Nella parola containerizzazione non può esserci nulla di più fondamentale del concetto di container.</p><p>Il sito delle <a href="https://www.docker.com/resources/what-container">risorse</a> ufficiali di Docker afferma che:</p><blockquote>Un contenitore è un'astrazione al livello dell'applicazione che impacchetta insieme il codice e le dipendenze. Invece di virtualizzare l'intera macchina fisica, i container virtualizzano solo il sistema operativo host.</blockquote><p>Puoi considerare i container come la prossima generazione di macchine virtuali.</p><p>Proprio come le macchine virtuali, i container sono ambienti completamente isolati dal sistema che li ospita, così come tra di loro. Sono anche molto più leggeri rispetto alle macchine virtuali tradizionali, quindi un gran numero di container può essere eseguito simultaneamente senza influenzare le prestazioni del sistema che li ospita.</p><p>I container e le macchine virtuali, in realtà, sono dei modi differenti di virtualizzare l'hardware fisico. La differenza principale tra i due è il metodo di virtualizzazione.</p><p>Le macchine virtuali sono solitamente create e gestite da un programma detto hypervisor (o ipervisore), come <a href="https://www.virtualbox.org/" rel="noopener noreferrer">Oracle VM VirtualBox</a>, <a href="https://www.vmware.com/" rel="noopener noreferrer">VMware Workstation</a>, <a href="https://www.linux-kvm.org/" rel="noopener noreferrer">KVM</a>, <a href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/" rel="noopener noreferrer">Microsoft Hyper-V </a>e via dicendo. Il programma hypervisor di solito è posto tra il sistema operativo e le macchina virtuale e agisce come mezzo di comunicazione.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/04/virtual-machines.svg" class="kg-image" alt="virtual-machines" width="600" height="400" loading="lazy"></figure><p>Ogni macchina virtuale possiede il suo sistema operativo guest, che è pesante tanto quanto un sistema operativo host.</p><p>L'applicazione all'interno di una macchina virtuale comunica con il sistema operativo guest, che parla con l'hypervisor, che a sua volta comunica con il sistema operativo host per allocare le risorse necessarie dall'infrastruttura fisica per eseguire l'applicazione.</p><p>Come puoi vedere, si tratta di una lunga catena di comunicazione tra le applicazioni in esecuzione all'interno della macchina virtuale e le infrastrutture fisiche. Le applicazioni in esecuzione all'interno della macchina virtuale potrebbero richiedere solo una piccola quantità di risorse, ma il sistema operativo guest aggiunge un notevole carico.</p><p>A differenza di una macchina virtuale, un container svolge il lavoro di virtualizzazione in un modo più intelligente. Invece di avere un sistema operativo guest completo all'interno del contenitore, utilizza il sistema operativo host attraverso il runtime del container, mantenendo al contempo l'isolamento – come una macchina virtuale tradizionale.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/04/containers.svg" class="kg-image" alt="containers" width="600" height="400" loading="lazy"></figure><p>Il runtime del container, ovvero Docker, si trova tra il container e il sistema operativo host al posto dell'hypervisor. I container comunicano con il runtime del container, che poi comunica con il sistema operativo host per ottenere le risorse necessarie dall'infrastruttura fisica.</p><p>Come risultato dell'eliminazione dell'intero livello del sistema operativo guest, i container sono molto più leggeri e meno avidi di risorse rispetto alle tradizionali macchine virtuali.</p><p>Come dimostrazione di questo punto, guarda il seguente blocco di codice:</p><pre><code>uname -a
# Linux alpha-centauri 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

docker run alpine uname -a
# Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux</code></pre><p>Nel blocco di codice qui sopra, ho eseguito il comando <code>uname -a</code> sul mio sistema operativo host per stampare i dettagli sul kernel. Poi, nella riga successiva, ho eseguito lo stesso comando all'interno del container di <a href="https://alpinelinux.org/" rel="noopener noreferrer">Alpine Linux</a>.</p><p>Come puoi vedere nell'output, il container sta effettivamente usando il kernel del sistema operativo host. Questo prova che i container virtualizzano il sistema operativo host invece di avere un proprio sistema operativo.</p><p>Se sei su una macchina Windows, scoprirai che tutti i container usano il kernel WSL2. Questo accade perché WSL2 agisce come back-end per Docker su Windows. Su macOS il back-end predefinito è una macchina virtuale con un hypervisor <a href="https://github.com/moby/hyperkit" rel="noopener noreferrer">HyperKit</a>.</p><h3 id="cos-un-immagine-docker"><strong>Cos'è un'immagine Docker?</strong></h3><p>Le immagini sono dei file autonomi a più livelli che &nbsp;agiscono come modello per la creazione di container. Sono come una copia congelata, di sola lettura di un container. Le immagini possono essere scambiate attraverso dei registri.</p><p>Nel passato, diversi motori di container avevano formati immagine diversi. Successivamente, L'<a href="https://opencontainers.org/">OCI (Open Container Initiative)</a> ha definito delle specifiche standard per le immagini di container a cui aderiscono i principali motori di containerizzazione. Ciò significa che un'immagine creata con Docker può essere usata con un altro runtime come Podman senza altre seccature.</p><p>I container sono semplicemente immagini nello stato di esecuzione. Quando ottieni un'immagine da internet ed esegui un container usando un'immagine, crei essenzialmente un altro livello scrivibile temporaneo sopra a quelli di sola lettura.</p><p>Questo concetto diventerà molto più chiaro nelle prossime sezioni di questo libro, ma per ora tieni a mente che le immagini sono file a più livelli e di sola lettura che portano un'applicazione nello stato desiderato al loro interno.</p><h3 id="cos-un-registro-docker"><strong>Cos'è un registro Docker?</strong></h3><p>Hai già imparato cosa sono due importanti pezzi del puzzle, i <em>container </em>e le <em>immagini</em>. Il pezzo finale è il <em>registro</em>.</p><p>Un registro di immagini è un luogo centralizzato in cui puoi caricare le tue immagini e scaricare le immagini create da altri. <a href="https://hub.docker.com/" rel="noopener noreferrer">Docker Hub</a> è il registro pubblico predefinito per Docker. Un altro registro di immagini molto popolare è <a href="https://quay.io/" rel="noopener noreferrer">Quay</a> di Red Hat.</p><p>In questo manuale ho scelto di utilizzare Docker Hub come registro.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-hub.png" class="kg-image" alt="docker-hub" width="600" height="400" loading="lazy"></figure><p>Puoi condividere gratuitamente un qualsiasi numero di immagini pubbliche su Docker Hub. Le persone in tutto il modo saranno in grado di scaricarle e usarle liberamente. Le immagini che ho caricato sono disponibili sulla pagina del mio profilo (<a href="https://hub.docker.com/u/fhsinchy" rel="noopener noreferrer">fhsinchy</a>).</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/my-images-on-docker-hub.png" class="kg-image" alt="my-images-on-docker-hub" width="600" height="400" loading="lazy"></figure><p>Oltre a Docker Hub o Quay, puoi anche creare il tuo registro di immagini in cui ospitare immagini private. Esiste anche un registro locale in esecuzione sul tuo computer che memorizza nella cache le immagini scaricate dai registri remoti.</p><h3 id="una-panoramica-dell-architettura-docker"><strong>Una panoramica dell'architettura Docker</strong></h3><p>Ora che hai un'idea dei concetti fondamentali della containerizzazione e di Docker, è tempo di capire come è stato progettato Docker come software.</p><p>Il motore consiste di tre componenti principali:</p><ol><li><strong><strong>Docker Daemon:</strong></strong> il <em>demone </em>(<code>dockerd</code>) è un processo che continua a essere eseguito in background e attende i comandi dal client. Il demone è in grado di gestire vari oggetti Docker.</li><li><strong><strong>Docker Client:</strong></strong> Il client (<code>docker</code>) è un'interfaccia da riga di comando principalmente responsabile del trasporto dei comandi lanciati dagli utenti.</li><li><strong><strong>REST API:</strong></strong> l'API REST agisce come un ponte tra il demone e il client. Ogni comando lanciato usando il client passa per l'API per raggiungere il demone alla fine.</li></ol><p>Secondo la <a href="https://docs.docker.com/get-started/overview/#docker-architecture">documentazione ufficiale</a>:</p><blockquote>"Docker utilizza un'architettura client-server. Il client di Docker comunica con il demone, che svolge il lavoro sporco di fare il build, eseguire e distribuire i container Docker".</blockquote><p>Come utente, eseguirai i comandi usando il componente client, il quale userà l'API REST per comunicare con il demone in esecuzione prolungata e svolgere il lavoro.</p><h3 id="il-quadro-completo"><strong>Il quadro completo</strong></h3><p>Ok, basta chiacchiere. È il momento di capire come lavorano in armonia tutti i pezzi del puzzle che hai appena conosciuto. Prima di tuffarci nelle spiegazioni di ciò che accade realmente quando esegui il comando <code>docker run hello-world</code>, ecco un piccolo diagramma che ho creato:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-run-hello-world.svg" class="kg-image" alt="docker-run-hello-world" width="600" height="400" loading="lazy"></figure><p>Questa immagine è una versione leggermente modificata di quella presente nella <a href="https://docs.docker.com/engine/images/architecture.svg">documentazione ufficiale</a>. Gli eventi che si verificano quando esegui il comando sono i seguenti:</p><ol><li>Esegui il comando <code>docker run hello-world</code> dove <code>hello-world</code> è il nome dell'immagine.</li><li>Il client di Docker comunica con il demone, dicendogli di prendere l'immagine <code>hello-world</code> ed eseguire un container per questa immagine.</li><li>Docker daemon cerca l'immagine nel tuo repository locale e si rende conto che non c'è, restituendo <code>Unable to find image 'hello-world:latest' locally</code> stampato sul terminale.</li><li>Il demone poi comunica con il registro pubblico, Docker Hub, e scarica l'ultima copia dell'immagine <code>hello-world</code>, indicata dalla riga <code>latest: Pulling from library/hello-world</code> nel terminale.</li><li>Docker daemon poi crea un nuovo container dall'immagine appena scaricata.</li><li>Infine Docker daemon esegue il container creato usando l'immagine <code>hello-world</code> generando come output il muro di testo sul tuo terminale.</li></ol><p>Cercare nell'hub le immagini non presenti localmente è il comportamento predefinito di Docker. Ma una volta che l'immagine è stata recuperata, starà nella cache locale. Quindi, se esegui ancora il comando, non vedrai le seguenti righe di output:</p><pre><code>Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
Status: Downloaded newer image for hello-world:latest</code></pre><p>Se c'è una nuova versione dell'immagine disponibile nel registro pubblico, il demone recupererà di nuovo l'immagine. <code>:latest</code> è un tag. Le immagini hanno solitamente dei tag significativi per indicare le versioni o i build. Ne riparleremo in dettaglio più avanti.</p><h2 id="le-basi-della-manipolazione-dei-container-in-docker"><strong>Le basi della manipolazione dei container in Docker</strong></h2><p>Nelle sezioni precedenti, hai imparato gli elementi costitutivi di Docker e hai anche eseguito un container usando il comando <code>docker run</code>.</p><p>In questa sezione, apprenderai come manipolare i container nel dettaglio. La manipolazione dei container è l'attività più comune che ti troverai a svolgere quotidianamente, quindi capire bene i vari comandi è cruciale.</p><p>Tieni a mente che questo non è un elenco esaustivo di tutti i comandi che puoi eseguire su Docker. Riporterò solo quelli più comuni. In qualsiasi momento tu voglia imparare di più sui comandi disponibili, visita i <a href="https://docs.docker.com/engine/reference/commandline/container/">riferimenti ufficiali</a> per la riga di comando Docker.</p><h3 id="come-eseguire-un-container"><strong>Come eseguire un container</strong></h3><p>Precedentemente hai usato <code>docker run</code> per creare e avviare un container usando l'immagine <code>hello-world</code>. La sintassi generica di questo comando è la seguente:</p><pre><code>docker run &lt;nome immagine&gt;</code></pre><p>Sebbene questo sia un comando perfettamente valido, c'è un modo migliore di inviare comandi al demone.</p><p>Prima della versione <code>1.13</code>, Docker aveva soltanto la sintassi precedentemente menzionata per quel comando. Successivamente, la riga di comando è stata <a href="https://www.docker.com/blog/whats-new-in-docker-1-13/">ristrutturata</a> per avere la seguente sintassi:</p><pre><code>docker &lt;oggetto&gt; &lt;comando&gt; &lt;opzioni&gt;</code></pre><p>In questa sintassi:</p><ul><li><code>oggetto</code> indica il tipo di oggetto Docker da manipolare. Può essere un oggetto <code>container</code>, <code>image</code>, <code>network</code> o <code>volume</code>.</li><li><code>comando</code> indica l'azione che deve essere svolta dal demone, cioè il comando <code>run</code>.</li><li><code>opzioni</code> può essere qualsiasi parametro valido che può sovrascrivere il comportamento predefinito del comando, come l'opzione <code>--publish</code> per il port mapping.</li></ul><p>Seguendo questa sintassi, il comando <code>run</code> può essere scritto come segue:</p><pre><code>docker container run &lt;nome immagine&gt;</code></pre><p><code>nome immagine</code> può appartenere a qualsiasi immagine da un registro online o sul tuo sistema locale. Come esempio, puoi provare a eseguire il container usando l'immagine <a href="https://hub.docker.com/r/fhsinchy/hello-dock" rel="noopener noreferrer">fhsinchy/hello-dock</a>. Questa immagine contiene una semplice applicazione <a href="https://vuejs.org/" rel="noopener noreferrer">Vue.js</a> che viene eseguita sulla porta 80 all'interno del container.</p><p>Per eseguire un container usando questa immagine, esegui il seguente comando sul terminale:</p><pre><code>docker container run --publish 8080:80 fhsinchy/hello-dock

# /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
# /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
# 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
# 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
# /docker-entrypoint.sh: Configuration complete; ready for start up</code></pre><p>Il comando è piuttosto esplicito. L'unica porzione che potrebbe richiedere qualche spiegazione è <code>--publish 8080:80</code>, di cui parleremo nella prossima sezione.</p><h3 id="come-pubblicare-una-porta"><strong>Come pubblicare una porta</strong></h3><p>I container sono ambienti isolati. Il tuo sistema host non sa nulla di ciò che accade in un container. Dunque le applicazioni all'interno di un container restano inaccessibili dall'esterno.</p><p>Per consentire l'accesso dall'esterno di un container, devi pubblicare la porta appropriata all'interno del container su una porta sulla tua rete locale. La sintassi comune per l'opzione <code>--publish</code> o <code>-p</code> è la seguente:</p><pre><code>--publish &lt;porta host&gt;:&lt;porta container&gt;</code></pre><p><code>--publish 8080:80</code> nella sezione precedente, vuol dire che ogni richiesta inviata alla porta 8080 del sistema host viene inoltrata alla porta 80 all'interno del container.</p><p>Per accedere all'applicazione sul browser, visita <code>http://127.0.0.1:8080</code>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" class="kg-image" alt="hello-dock" width="600" height="400" loading="lazy"></figure><p>Puoi stoppare il container semplicemente premendo la combinazione di tasti <code>ctrl + c</code> mentre la finestra del terminale è in focus, oppure chiudere completamente la finestra del terminale.</p><h3 id="come-usare-l-opzione-detach"><strong>Come usare l'opzione detach</strong></h3><p>Un'altra opzione molto popolare del comando <code>run</code> è <code>--detach</code> o <code>-d</code>. Nell'esempio precedente, per mantenere il container in esecuzione hai tenuto aperta la finestra del terminale. Chiudere la finestra del terminale avrebbe interrotto l'esecuzione del container.</p><p>Questo perché, per impostazione predefinita, i container vengono eseguiti in primo piano e si collegano al terminale come qualsiasi altro normale programma invocato del terminale.</p><p>Per sovrascrivere questo comportamento e mantenere il container in esecuzione in background, puoi includere l'opzione <code>--detach</code> con il comando <code>run</code> come segue:</p><pre><code>docker container run --detach --publish 8080:80 fhsinchy/hello-dock

# 9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc</code></pre><p>A differenza dell'esempio precedente, stavolta non otterrai un muro di testo. Invece, otterrai l'ID del container appena creato.</p><p>L'ordine delle opzioni che fornisci non è davvero importante. Se metti l'opzione <code>--publish</code> prima dell'opzione <code>--detach</code>, funziona allo stesso modo. Una cosa da tenere a mente per il comando <code>run</code> è che il nome dell'immagine deve essere inserito per ultimo. Se aggiungi qualsiasi cosa dopo il nome dell'immagine, sarà passato come argomento all'entry-point del container (vedi sezione <a href="#come-eseguire-comandi-all-interno-di-un-container">Come eseguire comandi all'interno di un container</a>) e potrebbe creare situazioni inaspettate.</p><h3 id="come-elencare-i-container"><strong>Come elencare i container</strong></h3><p>Il comando <code>container ls</code> può essere usato per elencare i container attualmente in esecuzione:</p><pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   5 seconds ago       Up 5 seconds        0.0.0.0:8080-&gt;80/tcp   gifted_sammet</code></pre><p>Un container chiamato <code>gifted_sammet</code> è in esecuzione. È stato creato 5 secondi fa (<code>5 seconds ago</code>) e lo status è <code>Up 5 seconds,</code> che indica che il container è stato correttamente in esecuzione dal momento della sua creazione.</p><p><code>CONTAINER ID</code> è <code>9f21cb777058</code>, i primi 12 caratteri dell'ID completo del container. L'ID completo è <code>9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc</code>, che è lungo 64 caratteri ed è stato stampato come output del comando <code>docker container run</code> nella sezione precedente.</p><p>La porta 8080 della tua rete locale, elencata nella colonna <code>PORTS</code>, sta puntando alla porta 80 all'interno del container. Il nome <code>gifted_sammet</code> è generato da Docker e può essere completamente diverso nel tuo computer.</p><p>Il comando <code>container ls</code> elenca solo i container attualmente in esecuzione sul tuo sistema. Per elencare tutti i container eseguiti in passato puoi usare l'opzione <code>--all</code> o <code>-a</code>.</p><pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS                  NAMES
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes               0.0.0.0:8080-&gt;80/tcp   gifted_sammet
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   3 minutes ago       Exited (0) 3 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 4 minutes ago       Exited (0) 4 minutes ago                          exciting_chebyshev</code></pre><p>Come puoi vedere il secondo container nella lista, <code>reverent_torvalds</code>, è stato creato prima e ha il codice di stato 0, che indica che non è stato prodotto nessun errore durante il runtime del container.</p><h3 id="come-dare-un-nome-a-un-container-o-rinominarlo"><strong>Come dare un nome a un container o rinominarlo</strong></h3><p>Di default, ogni container ha due identificatori:</p><ul><li>ID - una stringa composta da un numero casuale di 64 caratteri.</li><li>Nome - una combinazione di due parole casuali unite da un trattino basso.</li></ul><p>Fare riferimento a un container sulla base di due identificatori casuali è piuttosto sconveniente. Sarebbe ottimale poter riferirsi ai container usando un nome definito da te.</p><p>È possibile dare un nome a un container usando l'opzione <code>--name</code>. Per eseguire un altro container usando l'immagine <code>fhsinchy/hello-dock</code> con il nome <code>hello-dock-container</code> puoi eseguire il seguente comando:</p><pre><code>docker container run --detach --publish 8888:80 --name hello-dock-container fhsinchy/hello-dock

# b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb</code></pre><p>La porta 8080 sulla rete locale è occupata dal container <code>gifted_sammet</code> (creato nella sezione precedente). Ecco perché devi utilizzare un numero di porta diverso, come 8888. Ora fai una verifica con il comando <code>container ls</code>:</p><pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   28 seconds ago      Up 26 seconds       0.0.0.0:8888-&gt;80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   4 minutes ago       Up 4 minutes        0.0.0.0:8080-&gt;80/tcp   gifted_sammet</code></pre><p>È stato avviato un nuovo container con il nome <code>hello-dock-container</code>.</p><p>Puoi rinominare vecchi container usando il comando <code>container rename</code>. La sintassi per questo comando è la seguente:</p><pre><code>docker container rename &lt;identificatore container&gt; &lt;nuovo nome&gt;</code></pre><p>Per rinominare il container <code>gifted_sammet</code> in <code>hello-dock-container-2</code>, esegui il seguente comando:</p><pre><code>docker container rename gifted_sammet hello-dock-container-2</code></pre><p>Il comando non dà nessun output ma puoi verificare che i cambiamenti hanno avuto luogo usando il comando <code>container ls</code>. Il comando <code>rename</code> funziona sia per i container in esecuzione che per quelli stoppati.</p><h3 id="come-fermare-un-container-in-esecuzione"><strong>Come fermare un container in esecuzione</strong></h3><p>I container in esecuzione in primo piano possono essere bloccati semplicemente chiudendo la finestra del terminale o premendo <code>ctrl + c</code>. I container in esecuzione in background, tuttavia, non possono essere stoppati nello stesso modo.</p><p>Ci sono due comandi relativi a questa azione. Il primo è <code>container stop</code>, la cui sintassi generica è riportata di seguito:</p><pre><code>docker container stop &lt;identificatore container&gt;</code></pre><p>Dove <code>identificatore container</code> può essere sia l'id che il nome del container.</p><p>Spero ti ricordi del container che hai avviato nella sezione precedente. È ancora in esecuzione in background. Ottieni l'identificatore del container usando <code>docker container ls</code> (utilizzerò il container <code>hello-dock-container</code> per questa demo). Ora esegui il seguente comando per fermare l'esecuzione del container:</p><pre><code>docker container stop hello-dock-container

# hello-dock-container</code></pre><p>Se usi il nome come identificatore, otterrai il nome come output. Il comando <code>stop</code> ferma l'esecuzione di un container con grazia inviando un segnale <code>SIGTERM</code>. Se il container non si ferma entro un certo periodo, viene inviato un segnale <code>SIGKILL</code> che blocca immediatamente il container.</p><p>Nei casi in cui vuoi inviare un segnale <code>SIGKILL</code> invece di un segnale <code>SIGTERM</code>, puoi usare il comando <code>container kill</code>, che segue la stessa sintassi del comando <code>stop</code>.</p><pre><code>docker container kill hello-dock-container-2

# hello-dock-container-2</code></pre><h3 id="come-riavviare-un-container"><strong>Come riavviare un container</strong></h3><p>Parlando di riavviare, intendo specificamente due scenari:</p><ul><li>Riavviare un container che è stato precedentemente stoppato.</li><li>Riavviare un container in esecuzione.</li></ul><p>Come hai già imparato da una sezione precedente, i container stoppati restano nel tuo sistema. Se vuoi puoi riavviarli. Il comando <code>container start</code> può essere usato per avviare qualsiasi container stoppato o "ucciso". La sintassi è la seguente:</p><pre><code>docker container start &lt;identificatore container&gt;</code></pre><p>Puoi ottenere la lista di tutti i container eseguendo il comando <code>container ls --all</code>. Poi guarda i contained con lo status <code>Exited</code>.</p><pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                        PORTS               NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   3 minutes ago       Exited (0) 47 seconds ago                         hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   7 minutes ago       Exited (137) 17 seconds ago                       hello-dock-container-2
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   7 minutes ago       Exited (0) 7 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 9 minutes ago       Exited (0) 9 minutes ago                          exciting_chebyshev</code></pre><p>Ora per riavviare il container <code>hello-dock-container</code>, puoi eseguire il seguente comando:</p><pre><code>docker container start hello-dock-container

# hello-dock-container</code></pre><p>Puoi assicurarti che il contenitore sia in esecuzione guardando la lista dei container in esecuzione usando il comando <code>container ls</code>.</p><p>Il comando <code>container start</code> avvia qualsiasi container in modalità detached di default e mantiene ogni configurazione di porte fatta in precedenza. Quindi se visiti <code>http://127.0.0.1:8080</code>, dovresti essere in grado di accedere all'applicazione <code>hello-dock</code> proprio come prima.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" class="kg-image" alt="hello-dock" width="600" height="400" loading="lazy"></figure><p>In una situazione in cui desideri riavviare un container in esecuzione puoi usare il comando <code>container restart</code>, che segue la stessa sintassi del comando <code>container start</code>.</p><pre><code>docker container restart hello-dock-container-2

# hello-dock-container-2</code></pre><p>La differenza principale tra i due comandi è che il comando <code>container restart</code> tenta di fermare il container selezionato e poi lo riavvia, mentre il comando start riavvia un container già bloccato.</p><p>Nel caso di un container stoppato, entrambi i comandi vanno bene. Ma se il container è in esecuzione devi usare il comando <code>container restart</code>.</p><h3 id="come-creare-un-container-senza-eseguirlo"><strong>Come creare un container senza eseguirlo</strong></h3><p>In questa sezione, finora, hai avviato container usando il comando <code>container run</code>, che in realtà è la combinazione di due comandi separati:</p><ul><li><code>container create</code> crea un container da un'immagine.</li><li><code>container start</code> avvia un container che è stato già creato.</li></ul><p>Per svolgere la dimostrazione mostrata in precedenza usando questi due comandi, puoi fare qualcosa del genere:</p><pre><code>docker container create --publish 8080:80 fhsinchy/hello-dock

# 2e7ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
# 2e7ef5098bab        fhsinchy/hello-dock   "/docker-entrypoint.…"   30 seconds ago      Created                                 hello-dock</code></pre><p>Come si evince dall'output del comando <code>container ls --all</code>, è stato creato un container con il nome <code>hello-dock</code> usando l'immagine <code>fhsinchy/hello-dock</code>. Al momento lo <code>STATUS</code> del container è <code>Created</code> e, dato che non è in esecuzione, non verrà messo in elenco senza usare l'opzione <code>--all</code>.</p><p>Una volta che il container è stato creato, può essere avviato usando il comando <code>container start</code>.</p><pre><code>docker container start hello-dock

# hello-dock

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# 2e7ef5098bab        fhsinchy/hello-dock   "/docker-entrypoint.…"   About a minute ago   Up 29 seconds       0.0.0.0:8080-&gt;80/tcp   hello-dock</code></pre><p>Lo <code>STATUS</code> del container è cambiato da <code>Created</code> a <code>Up 29 seconds</code>, che indica che il container è in esecuzione. La configurazione delle porte viene mostrata nella colonna <code>PORTS</code> che era precedentemente vuota.‌</p><p>Sebbene tu possa farla franca con il comando <code>container run</code> nella maggior parte delle situazioni, più avanti nel manuale ci saranno alcuni casi che richiedono di usare il comando <code>container create</code>.</p><h3 id="come-rimuovere-dei-container-sospesi"><strong>Come rimuovere dei container sospesi</strong></h3><p>Come hai già visto, i container che hai stoppato o terminato restano nel sistema. Questi container sospesi possono occupare spazio o entrare in conflitto con dei nuovi container.</p><p>Per rimuovere un container stoppato puoi usare il comando <code>container rm</code>, con la seguente sintassi:</p><pre><code>docker container rm &lt;identificatore container&gt;</code></pre><p>Per trovare quali container non sono in esecuzione, usa il comando <code>container ls --all</code> e cerca i container con lo status <code>Exited</code>.</p><pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   6 minutes ago       Up About a minute           0.0.0.0:8888-&gt;80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   10 minutes ago      Up About a minute           0.0.0.0:8080-&gt;80/tcp   hello-dock-container-2
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   10 minutes ago      Exited (0) 10 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 12 minutes ago      Exited (0) 12 minutes ago                          exciting_chebyshev</code></pre><p>Come puoi vedere nell'output, i container con gli ID <code>6cf52771dde1</code> e <code>128ec8ceab71</code> non sono in esecuzione. Per rimuovere il container <code>6cf52771dde1</code> esegui il seguente comando:</p><pre><code>docker container rm 6cf52771dde1

# 6cf52771dde1</code></pre><p>Puoi controllare se il container è stato cancellato oppure no usando il comando <code>container ls</code>. Puoi anche rimuovere più container in un colpo solo passando i loro identificatori uno dopo l'altro separati da spazi.</p><p>Oppure, invece di rimuovere singoli container, se vuoi rimuovere tutti i container sospesi insieme, puoi usare il comando <code>container prune</code>.</p><p>Puoi controllare la lista dei container con il comando <code>container ls --all</code> per assicurarti che i container sospesi siano stati rimossi:</p><pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   8 minutes ago       Up 3 minutes        0.0.0.0:8888-&gt;80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   12 minutes ago      Up 3 minutes        0.0.0.0:8080-&gt;80/tcp   hello-dock-container-2</code></pre><p>Se finora hai seguito il libro esattamente, dovresti vedere in elenco soltanto <code>hello-dock-container</code> e <code>hello-dock-container-2</code>. Consiglio di fermare e rimuovere entrambi i container prima di proseguire con la prossima sezione.</p><p>Esiste anche l'opzione <code>--rm</code> per i comandi <code>container run</code> e <code>container start</code>, che indica che vuoi che i container siano rimossi non appena vengono stoppati. Per avviare un altro container <code>hello-dock</code> con l'opzione <code>--rm</code>, esegui il seguente comando:</p><pre><code>docker container run --rm --detach --publish 8888:80 --name hello-dock-volatile fhsinchy/hello-dock

# 0d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3</code></pre><p>Puoi usare il comando <code>container ls</code> per verificare che il container è in esecuzione:</p><pre><code>docker container ls

# CONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# 0d74e14091dc   fhsinchy/hello-dock   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:8888-&gt;80/tcp   hello-dock-volatile</code></pre><p>Se blocchi il container e poi verifichi di nuovo con il comando <code>container ls --all</code>:</p><pre><code>docker container stop hello-dock-volatile

# hello-dock-volatile

docker container ls --all

# CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES</code></pre><p>Il container è stato rimosso automaticamente. D'ora in poi, userò l'opzione <code>--rm</code> per la maggior parte dei container. Menzionerò esplicitamente qualora non dovesse essere necessaria.</p><h3 id="come-eseguire-un-container-in-modalit-interattiva"><strong>Come eseguire un container in modalità interattiva</strong></h3><p>Finora abbiamo eseguito solo container creati dalle immagini <a href="https://hub.docker.com/_/hello-world" rel="noopener noreferrer">hello-world</a> <a href="https://hub.docker.com/r/fhsinchy/hello-dock" rel="noopener noreferrer">fhsinchy/hello-dock</a>. Queste immagini sono realizzate per eseguire semplici programmi che non sono interattivi.</p><p>Beh, non tutte le immagini sono così semplici. Le immagini possono incapsulare un'intera distribuzione Linux al loro interno.</p><p>Distribuzioni popolari come <a href="https://ubuntu.com/" rel="noopener noreferrer">Ubuntu</a>, <a href="https://fedora.org/" rel="noopener noreferrer">Fedora</a> e <a href="https://debian.org/" rel="noopener noreferrer">Debian</a> hanno tutte delle immagini Docker ufficiali disponibili nell'hub. Linguaggi di programmazione come <a href="https://hub.docker.com/_/python" rel="noopener noreferrer">python</a>, <a href="https://hub.docker.com/_/php" rel="noopener noreferrer">php</a>, <a href="https://hub.docker.com/_/golang" rel="noopener noreferrer">go</a> oppure runtime come <a href="https://hub.docker.com/_/node" rel="noopener noreferrer">node</a> e <a href="https://hub.docker.com/r/hayd/deno" rel="noopener noreferrer">deno</a> hanno tutti delle immagini ufficiali.</p><p>Queste immagini non solo permettono di eseguire programmi pre-configurati. Sono anche configurate per eseguire una shell di default. Nel caso di immagini di sistemi operativi, può trattarsi di <code>sh</code> o <code>bash</code> e per linguaggi di programmazione o run-time, solitamente è il loro linguaggio shell predefinito.</p><p>Come puoi già sapere dalle tue esperienze con i computer, le shell sono programmi interattivi. Un'immagine configurata per eseguire un programma è un'immagine interattiva. Queste immagini richiedono un'opzione speciale <code>-it</code> da passare al comando <code>container run</code>.</p><p>Ad esempio, se esegui un container usando l'immagine di <code>ubuntu</code> eseguendo <code>docker container run ubuntu</code>, vedrai che non accade nulla. Ma se esegui lo stesso comando con l'opzione <code>-it</code>, dovresti arrivare direttamente sul bash all'interno del container di Ubuntu.</p><pre><code>docker container run --rm -it ubuntu

# root@dbb1f56b9563:/# cat /etc/os-release
# NAME="Ubuntu"
# VERSION="20.04.1 LTS (Focal Fossa)"
# ID=ubuntu
# ID_LIKE=debian
# PRETTY_NAME="Ubuntu 20.04.1 LTS"
# VERSION_ID="20.04"
# HOME_URL="https://www.ubuntu.com/"
# SUPPORT_URL="https://help.ubuntu.com/"
# BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
# PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
# VERSION_CODENAME=focal
# UBUNTU_CODENAME=focal</code></pre><p>Come puoi vedere dall'output del comando <code>cat /etc/os-release</code>, sto effettivamente interagendo con il bash in esecuzione nel container di Ubuntu.</p><p>L'opzione <code>-it</code> prepara il terreno in modo che tu possa interagire con qualsiasi programma interattivo all'interno di un container. Questa opzione, in realtà, è composta da due opzioni fuse insieme.</p><ul><li>L'opzione <code>-i</code> o <code>--interactive</code> ti connette al flusso di input del container, in modo da poter inviare input al bash.</li><li>L'opzione <code>-t</code> o <code>--tty</code> fa sì che tu ottenga una buona formattazione e l'esperienza del terminale nativo allocando uno pseudo-terminale.</li></ul><p>Devi usare l'opzione <code>-it</code> ogni volta che vuoi eseguire un container in modalità interattiva. Un altro esempio può essere l'esecuzione dell'immagine di <code>node</code> come segue:</p><pre><code>docker container run -it node

# Welcome to Node.js v15.0.0.
# Type ".help" for more information.
# &gt; ['farhan', 'hasin', 'chowdhury'].map(name =&gt; name.toUpperCase())
# [ 'FARHAN', 'HASIN', 'CHOWDHURY' ]</code></pre><p>Qualsiasi codice JavaScript valido può essere eseguito nella shell di node. Invece di scrivere <code>-it</code> puoi essere più verboso e scrivere separatamente <code>--interactive --tty</code>.</p><h3 id="come-eseguire-comandi-all-interno-di-un-container"><strong>Come eseguire comandi all'interno di un container</strong></h3><p>Nella sezione <a href="#hello-world-in-docker-introduzione-a-docker">Hello World in Docker</a> di questo manuale, mi hai visto eseguire un comando all'interno di un container di Alpine Linux. È andata più o meno così:</p><pre><code>docker run alpine uname -a
# Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux</code></pre><p>Ho eseguito il comando <code>uname -a</code> all'interno di un container di Alpine Linux. Casi come questo (dove tutto ciò che desideri è eseguire un certo comando all'interno di un container) sono piuttosto comuni.</p><p>Immagina di voler codificare una stringa usando il programma <code>base64</code>, disponibile su quasi ogni sistema operativo Linux o Unix (ma non su Windows).</p><p>In questa situazione, puoi avviare un container usando immagini come <a href="https://hub.docker.com/_/busybox" rel="noopener noreferrer">busybox</a> e fargli fare il lavoro.</p><p>La sintassi generica per codificare una stringa usando <code>base64</code> è la seguente:</p><pre><code>echo -n my-secret | base64

# bXktc2VjcmV0</code></pre><p>E la sintassi generica per passare un comando a un container che non è in esecuzione è la seguente:</p><pre><code>docker container run &lt;nome immagine&gt; &lt;comando&gt;</code></pre><p>Per svolgere la codifica base64 usando l'immagine, puoi eseguire il seguente comando:</p><pre><code>docker container run --rm busybox sh -c "echo -n my-secret | base64

# bXktc2VjcmV0</code></pre><p>In un comando <code>container run</code>, qualsiasi cosa passi dopo il nome dell'immagine viene passata all'entry-point predefinito dell'immagine.</p><p>Un entry-point è come un gateway all'immagine. La maggior parte dell'immagini, eccetto le immagini eseguibili (spiegate nella sezione <a href="#come-lavorare-con-immagini-eseguibili">Come lavorare con immagini eseguibili</a>), usano la shell o <code>sh</code> come entry-point predefinito. Quindi ogni comando shell valido può essere passato come argomento. </p><h3 id="come-lavorare-con-immagini-eseguibili"><strong>Come lavorare con immagini eseguibili</strong></h3><p>Nella sezione precedente, ho menzionato brevemente le immagini eseguibili. Queste immagini sono progettate per comportarsi come programmi eseguibili.</p><p>Prendi come esempio il mio progetto <a href="https://github.com/fhsinchy/rmbyext" rel="noopener noreferrer">rmbyext</a>. Si tratta di un semplice script Python in grado di cancellare file di una data estensione in modo ricorsivo. Per saperne di più, dai un'occhiata al repository:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/fhsinchy/rmbyext"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - fhsinchy/rmbyext: Recursively removes all files with given extension(s)</div><div class="kg-bookmark-description">Recursively removes all files with given extension(s) - GitHub - fhsinchy/rmbyext: Recursively removes all files with given extension(s)</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" width="32" height="32" alt="favicon" loading="lazy"><span class="kg-bookmark-author">fhsinchy</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/3962279bc22aac2d938371ee13beee0b4be6d6ebeb4dae792a026923b5f3f872/fhsinchy/rmbyext" width="1200" height="600" alt="rmbyext" loading="lazy"></div></a><figcaption>dammi una ⭐ per tenermi motivato</figcaption></figure><p>Se hai sia Git che Python installati, puoi installare questo script eseguendo il seguente comando:</p><pre><code>pip install git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext</code></pre><p>Assumendo che Python sia configurato correttamente nel tuo sistema, lo script dovrebbe essere disponibile ovunque tramite il terminale. La sintassi generica per questo script è la seguente:</p><pre><code>rmbyext &lt;estensione file&gt;</code></pre><p>Per fare una prova, apri il terminale all'interno di una cartella vuota e crea dei file con diverse estensioni. Per farlo puoi usare il comando <code>touch</code>. Ora ho una cartella sul mio computer con i seguenti file:</p><pre><code>touch a.pdf b.pdf c.txt d.pdf e.txt

ls

# a.pdf  b.pdf  c.txt  d.pdf  e.txt</code></pre><p>Per cancellare tutti i file <code>pdf</code> da questa cartella, puoi eseguire il seguente comando:</p><pre><code>rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf</code></pre><p>Un'immagine eseguibile per questo programma dovrebbe essere in grado di prendere le estensioni dei file come argomenti e cancellarli proprio come ha fatto il programma <code>rmbyext</code>.</p><p>L'immagine <a href="https://hub.docker.com/r/fhsinchy/rmbyext" rel="noopener noreferrer">fhsinchy/rmbyext</a> si comporta in modo simile. Questa immagine contiene una copia dello script <code>rmbyext</code> ed è configurata per eseguire lo script in una cartella <code>/zone</code> all'interno del container.</p><p>Ora il problema è che i container sono isolati dal sistema locale, quindi il programma <code>rmbyext</code> in esecuzione all'interno del container non ha accesso al file system locale. Se in qualche modo potessi creare una corrispondenza tra la cartella locale contenente i file <code>pdf</code> e la cartella <code>/zone</code> all'interno del container, i file sarebbero accessibili al container.</p><p>Un modo per garantire a un container l'accesso diretto al file system locale è usare un <a href="https://docs.docker.com/storage/bind-mounts/">bind mount</a>.</p><p>Un bind mount ti consente di formare un collegamento a due vie tra il contenuto di un file system della cartella locale (sorgente) e un'altra cartella all'interno di un container (destinazione). In questo modo, ogni modifica effettuata nella cartella di destinazione avrà effetto sulla cartella sorgente e viceversa.</p><p>Vediamo un bind mount in azione. Per eliminare i file usando questa immagine, invece del programma stesso, puoi eseguire il seguente comando:</p><pre><code>docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf</code></pre><p>Come potresti aver già indovinato vedendo la parte <code>-v $(pwd):/zone</code> del comando, l'opzione <code>-v</code> o <code>--volume</code> viene usata per creare un bind mount per un container. Questa opzione accetta tre campi separati da due punti (<code>:</code>). La sintassi generica per l'opzione è:</p><pre><code>--volume &lt;percorso assoluto cartella file system locale&gt;:&lt;percorso assoluto cartella file system container&gt;:&lt;accesso read write&gt;</code></pre><p>Il terzo campo è opzionale ma occorre passare il percorso assoluto della directory locale e quello della directory nel container.</p><p>La cartella sorgente nel mio caso è <code>/home/fhsinchy/the-zone</code>. Dato che il mio terminale è aperto nella cartella, <code>$(pwd)</code> sarà sostituito con <code>/home/fhsinchy/the-zone</code>, che contiene i file <code>.pdf</code> e <code>.txt</code> precedentemente menzionati.</p><p>Se vuoi saperne di più sulla sostituzione dei comandi puoi consultare <a href="https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html">questa risorsa</a> (in inglese).</p><p>L'opzione <code>--volume</code> o <code>-v</code> è valida per <code>container run</code> così come per il comando <code>container create</code>. Esploreremo i volumi in maggiore dettaglio nelle prossime sezioni, quindi non ti preoccupare se non li comprendi ancora bene qui.</p><p>La differenza tra un'immagine normale e una eseguibile è che l'entry-point di un'immagine eseguibile è impostato su un programma personalizzato invece di <code>sh</code>, in questo caso il programma <code>rmbyext</code>. E come hai imparato nelle sezioni precedenti, tutto ciò che scrivi dopo il nome dell'immagine in un comando <code>container run</code> viene passato all'entry-point dell'immagine.</p><p>Quindi, alla fine, il comando <code>docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf</code> si traduce in <code>rmbyext pdf</code> all'interno del container. Le immagini eseguibili non sono così comuni ma possono essere utili in certi casi.</p><h2 id="manipolazione-base-delle-immagini-docker"><strong>Manipolazione base delle immagini Docker</strong></h2><p>Ora che hai una buona comprensione di come eseguire dei container usando immagini disponibili pubblicamente, è tempo di imparare come creare le tue immagini.</p><p>In questa sezione, imparerai i fondamenti di come creare immagini, usarle per eseguire container e condividerle online.</p><p>Consiglio di installare <a href="https://code.visualstudio.com/" rel="noopener noreferrer">Visual Studio Code</a> con l'<a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker">estensione </a><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker" rel="noopener noreferrer">Docker</a> ufficiale dal marketplace. È un grande aiuto per l'esperienza di sviluppo.</p><h3 id="come-creare-un-immagine-docker"><strong>Come creare un'immagine Docker</strong></h3><p>Come ho già spiegato nella sezione <a href="#hello-world-in-docker-introduzione-a-docker">Hello World in Docker</a>, le immagini sono dei file indipendenti a più livelli che agiscono come modello per creare i container Docker. Sono come una copia congelata, di sola lettura di un container.</p><p>Per creare un'immagine usando uno dei tuoi programmi, devi avere una chiara visione di cosa vuoi dall'immagine. Prendi l'immagine ufficiale di <a href="https://hub.docker.com/_/nginx" rel="noopener noreferrer">nginx</a> come esempio. Puoi avviare un container usando questa immagine eseguendo il seguente comando:</p><pre><code>docker container run --rm --detach --name default-nginx --publish 8080:80 nginx

# b379ecd5b6b9ae27c144e4fa12bdc5d0635543666f75c14039eea8d5f38e3f56

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b379ecd5b6b9        nginx               "/docker-entrypoint.…"   8 seconds ago       Up 8 seconds        0.0.0.0:8080-&gt;80/tcp   default-nginx</code></pre><p>Se vai su <code>http://127.0.0.1:8080</code> nel browser, vedrai una pagina di risposta predefinita.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" class="kg-image" alt="nginx-default" width="600" height="400" loading="lazy"></figure><p>Tutto perfetto, ma se volessimo realizzare un'immagine personalizzata di NGINX che funzioni esattamente come quella ufficiale? Onestamente, è una situazione assolutamente plausibile. Quindi facciamolo.</p><p>Per creare un'immagine NGINX personalizzata, devi avere un'idea chiara dello stato finale dell'immagine. Secondo me, l'immagine dovrebbe essere come segue:</p><ul><li>L'immagine dovrebbe avere NGINX preinstallato, il che può essere fatto usando un gestore di pacchetti o può essere generato dalla sorgente.</li><li>L'immagine dovrebbe avviare automaticamente NGINX quando in esecuzione.</li></ul><p>Tutto semplice. Se hai clonato il repository del progetto linkato in questo libro, vai all'interno della radice del progetto e cerca una cartella chiamata <code>custom-nginx</code>.</p><p>Ora crea un nuovo file chiamato <code>Dockerfile</code> all'interno di quella cartella. <code>Dockerfile</code> è un insieme di istruzioni che, una volta processate dal demone, danno come risultato un'immagine. Il contenuto di <code>Dockerfile</code> è il seguente:</p><pre><code class="language-dockerfile">FROM ubuntu:latest

EXPOSE 80

RUN apt-get update &amp;&amp; \
    apt-get install nginx -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Le immagini sono file a più livelli e in questo file, ogni riga (detta istruzione) che scrivi crea un livello dell'immagine.</p><ul><li>Ogni <code>Dockerfile</code> valido inizia con l'istruzione <code>FROM</code>. Questa istruzione imposta la base dell'immagine risultante. Impostando <code>ubuntu:latest</code> come immagine base, ottieni tutte le qualità di Ubuntu già disponibili nella tua immagine personalizzata, così da poter usare cose come il comando <code>apt-get</code> per installare facilmente i pacchetti.</li><li>L'istruzione <code>EXPOSE</code> è usata per indicare la porta che deve essere pubblicata. Usare questa istruzione non vuol dire che non c'è bisogno di usare esplicitamente l'opzione <code>--publish</code>. L'istruzione <code>EXPOSE</code> funziona esattamente come una documentazione per qualcuno che sta cercando di eseguire un container usando la tua immagine. Ha anche altri utilizzi che non discuterò qui.</li><li>L'istruzione <code>RUN</code> in un <code>Dockerfile</code> esegue un comando all'interno della shell. Il comando <code>apt-get update &amp;&amp; apt-get install nginx -y</code> controlla le versioni aggiornate del pacchetto e installa NGINX. Il comando <code>apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code> è usato per pulire la cache del pacchetto, per non avere fardelli inutili nell'immagine. Questi due sono semplici comandi di Ubuntu, nulla di complesso. Le istruzioni <code>RUN</code> qui sono scritte in forma <code>shell</code>. Possono anche essere scritte in forma <code>exec</code>. Per saperne di più, puoi consultare i <a href="https://docs.docker.com/engine/reference/builder/#run">riferimenti ufficiali</a>.</li><li>Infine, l'istruzione <code>CMD</code> definisce il comando di default per l'immagine. Questa istruzione è scritta in forma <code>exec</code>, costituita da tre parti separate. <code>nginx</code> si riferisce all'eseguibile NGINX. <code>-g</code> e <code>daemon off</code> sono opzioni per NGINX. Eseguire NGINX come singolo processo all'interno del container è considerata una buona pratica, da cui l'utilizzo di questa opzione. L'istruzione <code>CMD</code> Può ancora essere scritta in forma <code>shell</code>. Per saperne di più, puoi consultare i <a href="https://docs.docker.com/engine/reference/builder/#cmd">riferimenti ufficiali</a>.</li></ul><p>Ora che hai un <code>Dockerfile</code> valido, da questo puoi costruire un'immagine. Proprio come i comandi relativi ai container, i comandi per le immagini possono essere lanciati usando la seguente sintassi:</p><pre><code>docker image &lt;comando&gt; &lt;opzioni&gt;</code></pre><p>Per costruire un'immagine usando il <code>Dockerfile</code> che hai scritto, apri il terminale all'interno della cartella <code>custom-nginx</code> ed esegui il seguente comando:</p><pre><code>docker image build .

# Sending build context to Docker daemon  3.584kB
# Step 1/4 : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step 2/4 : EXPOSE 80
#  ---&gt; Running in 9eae86582ec7
# Removing intermediate container 9eae86582ec7
#  ---&gt; 8235bd799a56
# Step 3/4 : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; Running in a44725cbb3fa
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container a44725cbb3fa
#  ---&gt; 3066bd20292d
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 4792e4691660
# Removing intermediate container 4792e4691660
#  ---&gt; 3199372aa3fc
# Successfully built 3199372aa3fc</code></pre><p>Per eseguire il build di un'immagine, il demone ha bisogno di due informazioni molto specifiche, ovvero il nome del <code>Dockerfile</code> e il contesto di build. Nel comando precedente:</p><ul><li><code>docker image build</code> è il comando per eseguire il build dell'immagine. Il demone trova qualsiasi file chiamato <code>Dockerfile</code> all'interno del contesto.</li><li>Il <code>.</code> alla fine definisce il contesto per questo build. Il contesto corrisponde alla cartella accessibile al demone durante il processo di build.</li></ul><p>Adesso, per eseguire un container usando questa immagine, puoi usare il comando <code>container run</code> abbinato con l'ID dell'immagine che hai ottenuto come risultato del processo di build. Nel mio caso, l'id è <code>3199372aa3fc</code>, visibile nella riga <code>Successfully built 3199372aa3fc</code> del precedente blocco di codice.</p><pre><code>docker container run --rm --detach --name custom-nginx-packaged --publish 8080:80 3199372aa3fc

# ec09d4e1f70c903c3b954c8d7958421cdd1ae3d079b57f929e44131fbf8069a0

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# ec09d4e1f70c        3199372aa3fc        "nginx -g 'daemon of…"   23 seconds ago      Up 22 seconds       0.0.0.0:8080-&gt;80/tcp   custom-nginx-packaged</code></pre><p>Per controllare, visita <code>http://127.0.0.1:8080</code>, dove dovresti vedere la pagina di risposta predefinita.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" class="kg-image" alt="nginx-default" width="600" height="400" loading="lazy"></figure><h3 id="come-taggare-le-immagini-docker"><strong>Come taggare le immagini Docker</strong></h3><p>Proprio come per i container, puoi assegnare identificatori personalizzati alle tue immagini invece di fare affidamento sugli ID generati casualmente. Nel caso di un'immagine, l'identificatore viene detto <em>tag</em>. L'opzione <code>--tag</code> o <code>-t</code> viene usata in tali casi.</p><p>La sintassi generica per questa opzione è la seguente:</p><pre><code>--tag &lt;repository immagine&gt;:&lt;tag immagine&gt;</code></pre><p>Il repository è solitamente conosciuto come il nome dell'immagine e il tag indica un certo build o versione.</p><p>Prendi l'immagine ufficiale di <a href="https://hub.docker.com/_/mysql" rel="noopener noreferrer">mysql</a> come esempio. Se vuoi eseguire un container usando una specifica versione di MySQL, come la 5.7, puoi eseguire <code>docker container run mysql:5.7</code> dove <code>mysql</code> è il repository dell'immagine e <code>5.7</code> è il tag.</p><p>Per taggare l'immagine personalizzata NGINX con <code>custom-nginx:packaged</code> puoi eseguire il seguente comando:</p><pre><code>docker image build --tag custom-nginx:packaged .

# Sending build context to Docker daemon  1.055MB
# Step 1/4 : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step 2/4 : EXPOSE 80
#  ---&gt; Running in 53ab370b9efc
# Removing intermediate container 53ab370b9efc
#  ---&gt; 6d6460a74447
# Step 3/4 : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; Running in b4951b6b48bb
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container b4951b6b48bb
#  ---&gt; fdc6cdd8925a
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3bdbd2af4f0e
# Removing intermediate container 3bdbd2af4f0e
#  ---&gt; f8837621b99d
# Successfully built f8837621b99d
# Successfully tagged custom-nginx:packaged</code></pre><p>Nulla cambierà eccetto il fatto che adesso puoi fare riferimento all'immagine con <code>custom-nginx:packaged</code> invece di una lunga stringa casuale.</p><p>Nei casi in cui ti dimentichi di taggare un'immagine durante il tempo di build, o magari vuoi cambiare il tag, puoi usare il comando <code>image tag</code> per farlo:</p><pre><code>docker image tag &lt;id immagine&gt; &lt;repository immagine&gt;:&lt;tag immagine&gt;

## oppure ##

docker image tag &lt;repository immagine&gt;:&lt;tag immagine&gt; &lt;nuovo repository immagine&gt;:&lt;nuovo tag immagine&gt;</code></pre><h3 id="come-elencare-e-rimuovere-immagini-docker"><strong>Come elencare e rimuovere immagini Docker</strong></h3><p>Proprio come il comando <code>container ls</code>, puoi usare il comando <code>image ls</code> per elencare tutte le immagini nel tuo sistema locale:</p><pre><code>docker image ls

# REPOSITORY     TAG        IMAGE ID       CREATED         SIZE
# &lt;none&gt;         &lt;none&gt;     3199372aa3fc   7 seconds ago   132MB
# custom-nginx   packaged   f8837621b99d   4 minutes ago   132MB</code></pre><p>Le immagini elencate qui possono essere cancellate usando il comando <code>image rm</code>, la cui sintassi generica è:</p><pre><code>docker image rm &lt;identificatore immagine&gt;</code></pre><p>L'identificatore può essere l'ID dell'immagine o il repository dell'immagine. Se usi il repository, devi specificare anche il tag. Per cancellare l'immagine personalizzata <code>custom-nginx:packaged</code>, puoi eseguire il seguente comando:</p><pre><code>docker image rm custom-nginx:packaged

# Untagged: custom-nginx:packaged
# Deleted: sha256:f8837621b99d3388a9e78d9ce49fbb773017f770eea80470fb85e0052beae242
# Deleted: sha256:fdc6cdd8925ac25b9e0ed1c8539f96ad89ba1b21793d061e2349b62dd517dadf
# Deleted: sha256:c20e4aa46615fe512a4133089a5cd66f9b7da76366c96548790d5bf865bd49c4
# Deleted: sha256:6d6460a744475a357a2b631a4098aa1862d04510f3625feb316358536fcd8641</code></pre><p>Puoi anche usare il comando <code>image prune</code> per fare pulizia di tutte le immagini sospese non taggate, come segue:</p><pre><code>docker image prune --force

# Deleted Images:
# deleted: sha256:ba9558bdf2beda81b9acc652ce4931a85f0fc7f69dbc91b4efc4561ef7378aff
# deleted: sha256:ad9cc3ff27f0d192f8fa5fadebf813537e02e6ad472f6536847c4de183c02c81
# deleted: sha256:f1e9b82068d43c1bb04ff3e4f0085b9f8903a12b27196df7f1145aa9296c85e7
# deleted: sha256:ec16024aa036172544908ec4e5f842627d04ef99ee9b8d9aaa26b9c2a4b52baa

# Total reclaimed space: 59.19MB</code></pre><p>L'opzione <code>--force</code> o <code>-f</code> salta ogni domanda di conferma. Puoi anche usare l'opzione <code>--all</code> o <code>-a</code> per rimuovere tutte le immagini nella cache del tuo registro locale.</p><h3 id="come-comprendere-i-livelli-di-un-immagine-docker"><strong>Come comprendere i livelli di un'immagine Docker</strong></h3><p>Fin dall'inizio di questo libro, ho detto che le immagini sono file a più livelli. In questa sezione, mostrerò i vari livelli di un'immagine e come giocano un ruolo importante nel processo di build dell'immagine.</p><p>Per questa dimostrazione, utilizzerò l'immagine <code>custom-nginx:packaged</code> della sezione precedente.</p><p>Per visualizzare i molti livelli di un'immagine, puoi usare il comando <code>image history</code>. I vari livelli dell'immagine <code>custom-nginx:packaged</code> possono essere visualizzati come segue:</p><pre><code>docker image history custom-nginx:packaged

# IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
# 7f16387f7307        5 minutes ago       /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B                             
# 587c805fe8df        5 minutes ago       /bin/sh -c apt-get update &amp;&amp;     apt-get ins…   60MB                
# 6fe4e51e35c1        6 minutes ago       /bin/sh -c #(nop)  EXPOSE 80                    0B                  
# d70eaf7277ea        17 hours ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c mkdir -p /run/systemd &amp;&amp; echo 'do…   7B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c [ -z "$(apt-get indextargets)" ]     0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c set -xe   &amp;&amp; echo '#!/bin/sh' &gt; /…   811B                
# &lt;missing&gt;           17 hours ago        /bin/sh -c #(nop) ADD file:435d9776fdd3a1834…   72.9MB</code></pre><p>Ci sono otto livelli in questa immagine. Quello superiore è l'ultimo e man mano che scendi diventano meno recenti. Il livello superiore è quello che di solito si usa per eseguire i container.</p><p>Ora, diamo un'occhiata da vicino alle immagini partendo dall'immagine <code>d70eaf7277ea</code> fino a <code>7f16387f7307</code>. Ignorerò i quattro livelli inferiori in cui <code>IMAGE</code> è <code>&lt;missing&gt;</code> (mancante), che non sono una nostra preoccupazione.</p><ul><li><code>d70eaf7277ea</code> è stato creato da <code>/bin/sh -c #(nop) &nbsp;CMD ["/bin/bash"]</code> che indica che la shell predefinita all'interno di Ubuntu è stata caricata con successo.</li><li><code>6fe4e51e35c1</code> è stato creato da <code>/bin/sh -c #(nop) &nbsp;EXPOSE 80</code> che era la seconda istruzione nel codice.</li><li><code>587c805fe8df</code> è stato creato da <code>/bin/sh -c apt-get update &amp;&amp; apt-get install nginx -y &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code>, la terza istruzione nel codice. Puoi anche vedere che questa immagine ha una dimensione di <code>60MB</code> visti tutti i pacchetti che sono stati installati durante l'esecuzione di questa istruzione.</li><li>Infine, il livello più alto <code>7f16387f7307</code> è stato creato da <code>/bin/sh -c #(nop) &nbsp;CMD ["nginx", "-g", "daemon off;"]</code>, che definisce il comando di default per quest'immagine.</li></ul><p>Come puoi vedere, l'immagine comprende molti livelli di sola lettura, ognuno dei quali registra un nuovo set di cambiamenti allo stato innescati da determinate istruzioni. Quando avvii un container usando un'immagine, ottieni un nuovo livello scrivibile sopra agli altri livelli.</p><p>Questo fenomeno di stratificazione che avviene ogni volta che lavori con Docker è reso possibile da un magnifico concetto tecnico detto union file system. Union si riferisce all'unione nella teoria degli insiemi. Secondo <a href="https://it.wikipedia.org/wiki/UnionFS">Wikipedia</a>:</p><blockquote>Consente di sovrapporre in modo trasparente file e directory di <em>file system</em> separati, detti <em>branch</em> (rami), per formare un singolo <em>file system</em> coerente. I contenuti delle directory che hanno lo stesso percorso nei rami uniti, saranno visti come se fossero nella stessa directory del <em>file system</em> virtuale.</blockquote><p>Utilizzando questo concetto, Docker può evitare la duplicazione dei dati e utilizzare i livelli precedentemente creati come cache per i build successivi. Ciò dà come risultato immagini compatte ed efficienti che possono essere usate ovunque.</p><h3 id="come-eseguire-il-build-di-nginx-dalla-sorgente"><strong>Come eseguire il build di NGINX dalla sorgente</strong></h3><p>Nella sezione precedente, hai imparato le istruzioni <code>FROM</code>, <code>EXPOSE</code>, <code>RUN</code> e <code>CMD</code>. In questa sezione, parleremo molto di altre istruzioni.</p><p>Creeremo ancora un'immagine NGINX personalizzata. Ma invece di usare un gestore di pacchetti come <code>apt-get</code> dell'esempio precedente, farai il build di NGINX dalla sorgente.</p><p>Per eseguire il build di NGINX dalla sorgente, hai bisogno del file sorgente di NGINX. Se hai clonato il mio repository, vedrai un file chiamato <code>nginx-1.19.2.tar.gz</code> nella cartella <code>custom-nginx</code>. Userai questo archivio come sorgente per fare il build di NGINX.</p><p>Prima di immergerci nel codice, pianifichiamo il processo. La creazione dell'immagine stavolta può essere fatta nei sette passaggi elencati di seguito:</p><ul><li>Ottenere una buona immagine di base per il build dell'applicazione, come <a href="https://hub.docker.com/_/ubuntu" rel="noopener noreferrer">ubuntu</a>.</li><li>Installare le dipendenze di build necessarie sull'immagine di base.</li><li>Copiare il file <code>nginx-1.19.2.tar.gz</code> all'interno dell'immagine.</li><li>Estrarre i contenuti dell'archivio e liberarsene.</li><li>Configurare il build, compilare e installare il programma usando lo strumento <code>make</code>.</li><li>Liberarsi del codice sorgente estratto.</li><li>Lanciare l'eseguibile <code>nginx</code>.</li></ul><p>Ora che abbiamo un piano, inizia aprendo il vecchio <code>Dockerfile</code>, aggiornandone il contenuto come segue:</p><pre><code>FROM ubuntu:latest

RUN apt-get update &amp;&amp; \
    apt-get install build-essential\ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.1 \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

COPY nginx-1.19.2.tar.gz .

RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz

RUN cd nginx-1.19.2 &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install

RUN rm -rf /nginx-1.19.2

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Come puoi vedere, il codice all'interno di <code>Dockerfile</code> riflette i sette passaggi di cui ho parlato.</p><ul><li>L'istruzione <code>FROM</code> definisce Ubuntu come immagine di base creando un ambiente ideale per il build di qualsiasi applicazione.</li><li>L'istruzione <code>RUN</code> installa i pacchetti standard necessari per il build di NGINX dalla sorgente.</li><li>L'istruzione <code>COPY</code> è qualcosa di nuovo. Questa istruzione è responsabile di copiare il file <code>nginx-1.19.2.tar.gz</code> all'interno dell'immagine. La sintassi generica dell'istruzione <code>COPY</code> è <code>COPY &lt;sorgente&gt; &lt;destinazione&gt;</code>, dove la sorgente è nel tuo file system locale e la destinazione è all'interno dell'immagine. <code>.</code> come destinazione si riferisce alla cartella di lavoro all'interno dell'immagine che è di default <code>/</code>, se non impostata diversamente.</li><li>La seconda istruzione <code>RUN</code> estrae il contenuto dall'archivio usando <code>tar</code> e in seguito se ne sbarazza.</li><li>Il file di archivio contiene una cartella chiamata <code>nginx-1.19.2</code> con il codice sorgente. Quindi, nel prossimo passaggio, dovrai spostarti in questa directory (<code>cd</code>) e svolgere il processo di build. Se vuoi imparare di più su questo argomento puoi leggere<a href="https://itsfoss.com/install-software-from-source-code/"> questo articolo</a> (risorsa in inglese).</li><li>Una volta che il build e l'installazione sono completati, rimuovi la cartella <code>nginx-1.19.2</code> usando il comando <code>rm</code>.</li><li>Nello step finale, avvia NGINX nella modalità a processo singolo, proprio come hai fatto prima.</li></ul><p>Per fare il build di un'immagine usando questo codice, esegui il seguente comando:</p><pre><code>docker image build --tag custom-nginx:built .

# Step 1/7 : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step 2/7 : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; Running in 2d0aa912ea47
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 2d0aa912ea47
#  ---&gt; cbe1ced3da11
# Step 3/7 : COPY nginx-1.19.2.tar.gz .
#  ---&gt; 7202902edf3f
# Step 4/7 : RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz
 ---&gt; Running in 4a4a95643020
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 4a4a95643020
#  ---&gt; f9dec072d6d6
# Step 5/7 : RUN cd nginx-1.19.2 &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in b07ba12f921e
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container b07ba12f921e
#  ---&gt; 5a877edafd8b
# Step 6/7 : RUN rm -rf /nginx-1.19.2
#  ---&gt; Running in 947e1d9ba828
# Removing intermediate container 947e1d9ba828
#  ---&gt; a7702dc7abb7
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3110c7fdbd57
# Removing intermediate container 3110c7fdbd57
#  ---&gt; eae55f7369d3
# Successfully built eae55f7369d3
# Successfully tagged custom-nginx:built</code></pre><p>Questo codice va bene, ma ci sono dei punti in cui possiamo fare dei miglioramenti.</p><ul><li>Invece di scrivere il nome del file in codifica fissa come <code>nginx-1.19.2.tar.gz</code>, puoi creare un argomento usando l'istruzione <code>ARG</code>. In questo modo, sarai in grado di cambiare la versione o il nome del file cambiando l'argomento.</li><li>Invece di scaricare manualmente l'archivio, puoi far scaricare il file al demone durante il processo di build. Esiste un'altra istruzione come <code>COPY</code>, chiamata <code>ADD</code>, che ti consente di aggiungere dei file da internet.</li></ul><p>Apri il file <code>Dockerfile</code> e aggiorna il suo contenuto come segue:</p><pre><code class="language-dockerfile">FROM ubuntu:latest

RUN apt-get update &amp;&amp; \
    apt-get install build-essential\ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.1 \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

ARG FILENAME="nginx-1.19.2"
ARG EXTENSION="tar.gz"

ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .

RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}

RUN cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install

RUN rm -rf /${FILENAME}}

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Il codice è quasi identico al blocco di codice precedente, eccetto per delle nuove istruzioni <code>ARG</code> sulle righe 13, 14 e per l'uso dell'istruzione <code>ADD</code> sulla riga 16. La spiegazione per il codice aggiornato è riportata di seguito:</p><ul><li>L'istruzione <code>ARG</code> ti permette di dichiarare variabili come in altri linguaggi. Queste variabili o argomenti possono essere consultate in seguito usando la sintassi <code>${nome argomento}</code>. Qui ho usato <code>nginx-1.19.2</code> come nome del file e <code>tar.gz</code> come estensione in due argomenti separati. In questo modo posso passare a nuove versioni di NGINX o al formato archivio facendo una modifica in un solo posto. Nel codice qui sopra, ho aggiunto i valori predefiniti alle variabili. I valori delle variabili possono anche essere passati come opzioni del comando <code>image build</code>. Puoi consultare i <a href="https://docs.docker.com/engine/reference/builder/#arg">riferimenti ufficiali</a> per maggiori dettagli.</li><li>Nell'istruzione <code>ADD</code>, ho formato l'URL del download dinamicamente, usando gli argomenti dichiarati in precedenza. La riga <code>https://nginx.org/download/${FILENAME}.${EXTENSION}</code> darà come risultato <code>https://nginx.org/download/nginx-1.19.2.tar.gz</code> durante il processo di build. Puoi cambiare la versione del file e l'estensione soltanto in un punto grazie all'istruzione <code>ARG</code>.</li><li>L'istruzione <code>ADD</code> non estrae i file ottenuti da internet in modo predefinito, per cui l'utilizzo di <code>tar</code> nella riga 18.</li></ul><p>Il resto del codice è praticamente invariato. Adesso dovresti essere in grado di capire da solo l'utilizzo degli argomenti. Infine, proviamo a fare il build dell'immagine da questo codice aggiornato.</p><pre><code>docker image build --tag custom-nginx:built .

# Step 1/9 : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step 2/9 : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged custom-nginx:built</code></pre><p>Ora dovresti essere in grado di eseguire un container usando l'immagine <code>custom-nginx:built</code>.</p><pre><code>docker container run --rm --detach --name custom-nginx-built --publish 8080:80 custom-nginx:built

# 90ccdbc0b598dddc4199451b2f30a942249d85a8ed21da3c8d14612f17eed0aa

docker container ls

# CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                  NAMES
# 90ccdbc0b598        custom-nginx:built   "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes        0.0.0.0:8080-&gt;80/tcp   custom-nginx-built</code></pre><p>Un container che usa l'immagine <code>custom-nginx:built-v2</code> è stato eseguito con successo. Il container dovrebbe essere accessibile all'indirizzo <code>http://127.0.0.1:8080</code>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" class="kg-image" alt="nginx-default" width="600" height="400" loading="lazy"></figure><p>Ed ecco la pagina di risposta predefinita di NGINX. Puoi visitare il s<a href="https://docs.docker.com/engine/reference/builder/">ito di riferimento ufficiale</a> per imparare altro sulle istruzioni disponibili.</p><h3 id="come-ottimizzare-le-immagini-docker"><strong>Come ottimizzare le immagini Docker</strong></h3><p>L'immagine di cui abbiamo fatto il build nell'ultima sezione è funzionale ma non ottimizzata. Per provarlo, diamo un'occhiata alla dimensione dell'immagine usando il comando <code>image ls</code>:</p><pre><code>docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     1f3aaf40bb54   16 minutes ago   343MB</code></pre><p>Per un'immagine contenente solo NGINX è troppo. Se scarichi l'immagine ufficiale e verifichi la sua dimensione, vedrai quanto è ridotta:</p><pre><code>docker image pull nginx:stable

# stable: Pulling from library/nginx
# a076a628af6f: Pull complete 
# 45d7b5d3927d: Pull complete 
# 5e326fece82e: Pull complete 
# 30c386181b68: Pull complete 
# b15158e9ebbe: Pull complete 
# Digest: sha256:ebd0fd56eb30543a9195280eb81af2a9a8e6143496accd6a217c14b06acd1419
# Status: Downloaded newer image for nginx:stable
# docker.io/library/nginx:stable

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     1f3aaf40bb54   25 minutes ago   343MB
# nginx              stable    b9e1dc12387a   11 days ago      133MB</code></pre><p>Per capirne la ragione, iniziamo dando uno sguardo a <code>Dockerfile</code>:</p><pre><code class="language-dockerfile">FROM ubuntu:latest

RUN apt-get update &amp;&amp; \
    apt-get install build-essential\ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.1 \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

ARG FILENAME="nginx-1.19.2"
ARG EXTENSION="tar.gz"

ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .

RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}

RUN cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install

RUN rm -rf /${FILENAME}}

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Come puoi vedere nella riga 3, l'istruzione <code>RUN</code> installa un sacco di cose. Sebbene questi pacchetti siano necessari per fare il build di NGINX dal sorgente, non sono necessari per la sua esecuzione.</p><p>Di 6 pacchetti che abbiamo installato, solo 2 sono necessari per eseguire NGINX. Si tratta di <code>libpcre3</code> e <code>zlib1g</code>. Quindi sarebbe una buona idea disinstallare gli altri pacchetti una volta che il processo di build è stato completato.</p><p>Per farlo, aggiorniamo <code>Dockerfile</code> come segue:</p><pre><code class="language-dockerfile">FROM ubuntu:latest

EXPOSE 80

ARG FILENAME="nginx-1.19.2"
ARG EXTENSION="tar.gz"

ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .

RUN apt-get update &amp;&amp; \
    apt-get install build-essential \ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.1 \
                    libssl-dev \
                    -y &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp; \
    apt-get remove build-essential \ 
                    libpcre3-dev \
                    zlib1g-dev \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get autoremove -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Come puoi vedere, nella riga 10 una singola istruzione <code>RUN</code> sta facendo tutto il lavoro pesante necessario. L'esatta catena di eventi è la seguente:</p><ul><li>Dalla riga 10 alla 17, vengono installati tutti i pacchetti necessari.</li><li>Nella riga 18, il codice viene estratto e l'archivio scaricato viene rimosso.</li><li>Dalla riga 19 alla 28, NGINX viene configurato, viene eseguito il build e poi viene installato sul sistema.</li><li>Nella riga 29, i file estratti dall'archivio scaricato vengono rimossi.</li><li>Dalla riga 30 alla 36, tutti i pacchetti non necessari vengono disinstallati e la cache ripulita. I pacchetti <code>libpcre3</code> e <code>zlib1g</code> sono necessari per eseguire NGINX quindi vengono mantenuti.</li></ul><p>Potresti chiederti perché sto facendo tutto questo lavoro in una singola istruzione <code>RUN</code> invece di separare il tutto in diverse istruzioni come abbiamo fatto in precedenza. Beh, dividerle sarebbe un errore.</p><p>Se installi i pacchetti e poi li rimuovi in istruzioni <code>RUN</code> separate, saranno in livelli diversi dell'immagine. Sebbene l'immagine finale non avrà i pacchetti rimossi, la loro dimensione sarà comunque aggiunta all'immagine finale dato che esistono in uno dei livelli che costituiscono l'immagine. Quindi assicurati di apportare questi cambiamenti in un solo livello.</p><p>Facciamo il build dell'immagine usando questo <code>Dockerfile</code> e guarda le differenze.</p><pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  1.057MB
# Step 1/7 : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step 2/7 : EXPOSE 80
#  ---&gt; Running in 006f39b75964
# Removing intermediate container 006f39b75964
#  ---&gt; 6943f7ef9376
# Step 3/7 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in ffaf89078594
# Removing intermediate container ffaf89078594
#  ---&gt; 91b5cdb6dabe
# Step 4/7 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in d0f5188444b6
# Removing intermediate container d0f5188444b6
#  ---&gt; 9626f941ccb2
# Step 5/7 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; a8e8dcca1be8
# Step 6/7 : RUN apt-get update &amp;&amp;     apt-get install build-essential                     libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apt-get remove build-essential                     libpcre3-dev                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get autoremove -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; Running in e5675cad1260
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container e5675cad1260
#  ---&gt; dc7e4161f975
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; 512aa6a95a93
# Successfully built 512aa6a95a93
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED              SIZE
# custom-nginx       built     512aa6a95a93   About a minute ago   81.6MB
# nginx              stable    b9e1dc12387a   11 days ago          133MB</code></pre><p>Come puoi vedere, la dimensione dell'immagine è passata da 343MB a 81.6MB. L'immagine ufficiale è 133MB. Questo è un build piuttosto ottimizzato, ma possiamo spingerci ancora oltre nella prossima sezione.</p><h3 id="alpine-linux"><strong>Alpine Linux</strong></h3><p>Se stai smanettando con i container da un po', potresti aver sentito parlare di <a href="https://alpinelinux.org/" rel="noopener noreferrer">Alpine Linux</a>. Si tratta di una distribuzione Linux completa come <a href="https://ubuntu.com/" rel="noopener noreferrer">Ubuntu</a>, <a href="https://www.debian.org/" rel="noopener noreferrer">Debian</a> o <a href="https://getfedora.org/" rel="noopener noreferrer">Fedora</a>.</p><p>Ma la cosa buona di Alpine è che è costruito attorno a <code>musl</code> <code>libc</code> e <code>busybox</code> ed è leggero. Mentre l'ultima immagine di <a href="https://hub.docker.com/_/ubuntu" rel="noopener noreferrer">ubuntu</a> pesa circa 28MB, <a href="https://hub.docker.com/_/alpine" rel="noopener noreferrer">alpine</a> pesa 2.8MB.</p><p>Oltre alla caratteristica leggerezza, Alpine è anche sicuro ed è molto più adatto per creare container rispetto ad altre distribuzioni.</p><p>Nonostante non sia user friendly come altre distribuzioni commerciali, la transizione ad Alpine è ancora molto semplice. In questa sezione, imparerai come ricreare l'immagine <code>custom-nginx</code> usando l'immagine di Alpine come base.</p><p>Apri <code>Dockerfile</code> e aggiorna il suo contenuto come segue:</p><pre><code class="language-dockerfile">FROM alpine:latest

EXPOSE 80

ARG FILENAME="nginx-1.19.2"
ARG EXTENSION="tar.gz"

ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .

RUN apk add --no-cache pcre zlib &amp;&amp; \
    apk add --no-cache \
            --virtual .build-deps \
            build-base \ 
            pcre-dev \
            zlib-dev \
            openssl-dev &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp; \
    apk del .build-deps

CMD ["nginx", "-g", "daemon off;"]</code></pre><p>Il codice è quasi identico, eccetto che per alcuni cambiamenti. Elencherò le modifiche e le spiegherò passo passo:</p><ul><li>Invece di usare <code>apt-get install</code> per installare i pacchetti, usiamo <code>apk add</code>. L'opzione <code>--no-cache</code> vuol dire che i pacchetti scaricati non saranno salvati nella cache. Allo stesso modo, useremo <code>apk del</code> invece di <code>apt-get remove</code> per disinstallare i pacchetti.</li><li>L'opzione <code>--virtual</code> per il comando <code>apk add</code> viene usata per impacchettare un gruppo di pacchetti in un solo pacchetto virtuale per una gestione più semplice. I pacchetti che sono necessari solo per il build del programma sono etichettati come <code>.build-deps</code> e vengono rimossi nella riga 29 eseguendo il comando <code>apk del .build-deps</code>. Per saperne di più puoi consultare la <a href="https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html#_virtuals">documentazione ufficiale</a>.</li><li>I nomi dei pacchetti sono un po' diversi qui. Di solito, ogni distribuzione Linux ha un suo repository dove cercare pacchetti disponibile per chiunque. Se conosci i pacchetti richiesti per una certa attività, puoi andare dritto all'apposito repository per una distribuzione e cercarlo. Puoi cercare i pacchetti per Alpine Linux <a href="https://pkgs.alpinelinux.org/packages">qui</a>.</li></ul><p>Ora fai il build di una nuova immagine usando questo <code>Dockerfile</code> e vedi la differenza nella dimensione del file:</p><pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  1.055MB
# Step 1/7 : FROM alpine:latest
#  ---&gt; 7731472c3f2a
# Step 2/7 : EXPOSE 80
#  ---&gt; Running in 8336cfaaa48d
# Removing intermediate container 8336cfaaa48d
#  ---&gt; d448a9049d01
# Step 3/7 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in bb8b2eae9d74
# Removing intermediate container bb8b2eae9d74
#  ---&gt; 87ca74f32fbe
# Step 4/7 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in aa09627fe48c
# Removing intermediate container aa09627fe48c
#  ---&gt; 70cb557adb10
# Step 5/7 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; b9790ce0c4d6
# Step 6/7 : RUN apk add --no-cache pcre zlib &amp;&amp;     apk add --no-cache             --virtual .build-deps             build-base             pcre-dev             zlib-dev             openssl-dev &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apk del .build-deps
#  ---&gt; Running in 0b301f64ffc1
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 0b301f64ffc1
#  ---&gt; dc7e4161f975
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; 3e186a3c6830
# Successfully built 3e186a3c6830
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
# custom-nginx       built     3e186a3c6830   8 seconds ago   12.8MB</code></pre><p>La versione ubuntu era 81.6MB, mentre la versione alpine è scesa fino a 12.8MB, un guadagno enorme. Oltre al gestore di pacchetti <code>apk</code>, ci sono alcune cose che differenziano Alpine da Ubuntu ma non sono così importanti. Puoi sempre fare una ricerca su internet se resti bloccato.</p><h3 id="come-creare-immagini-docker-eseguibili"><strong>Come creare immagini Docker eseguibili</strong></h3><p>Nella sezione precedente hai lavorato con l'immagine <a href="https://hub.docker.com/r/fhsinchy/rmbyext" rel="noopener noreferrer">fhsinchy/rmbyext</a>. In questa sezione imparerai come realizzare un'immagine eseguibile come quella.</p><p>Per iniziare, apri la cartella in cui hai clonato il repository in dotazione con questo libro. Il codice per l'applicazione <code>rmbyext</code> si trova all'interno della sotto-cartella con lo stesso nome.</p><p>Prima di iniziare a lavorare sul <code>Dockerfile</code>, prendi un momento per pianificare quale dovrebbe essere l'output finale. Secondo me, dovrebbe essere qualcosa del genere:</p><ul><li>L'immagine dovrebbe avere Python preinstallato.</li><li>Dovrebbe contenere una copia del mio script <code>rmbyext</code>.</li><li>Dovrebbe essere definita una cartella di lavoro in cui verrà eseguito lo script.</li><li>Lo script <code>rmbyext</code> dovrebbe essere impostato come entry-point in modo che l'immagine possa prendere i nomi delle estensioni come argomenti.</li></ul><p>Per fare il build dell'immagine sopra menzionata, segui i seguenti passaggi:</p><ul><li>Ottieni una buona immagine base per eseguire gli script Python, come <a href="https://hub.docker.com/_/python" rel="noopener noreferrer">python</a>.</li><li>Definisci la cartella di lavoro come una cartella di facile accesso.</li><li>Installa Git in modo che lo script possa essere installato dal mio repository GitHub.</li><li>Installa lo script usando Git e pip.</li><li>Sbarazzati dei pacchetti di build non necessari.</li><li>Imposta <code>rmbyext</code> come entry-point dell'immagine.</li></ul><p>Ora crea un nuovo <code>Dockerfile</code> nella cartella <code>rmbyext</code> e inserisci il seguente codice al suo interno:</p><pre><code class="language-dockerfile">FROM python:3-alpine

WORKDIR /zone

RUN apk add --no-cache git &amp;&amp; \
    pip install git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext &amp;&amp; \
    apk del git

ENTRYPOINT [ "rmbyext" ]</code></pre><p>La spiegazione delle istruzioni in questo file è la seguente:</p><ul><li>L'istruzione <code>FROM</code> definisce <a href="https://hub.docker.com/_/python" rel="noopener noreferrer">python</a> come immagine base, creando un ambiente ideale per eseguire gli script Python. Il tag <code>3-alpine</code> indica che vuoi la variante Alpine di Python 3.</li><li>L'istruzione <code>WORKDIR</code> imposta la cartella di lavoro predefinita su <code>/zone</code>. Il nome della cartella di lavoro è completamente casuale qui. Ho usato zone, ma tu puoi usare il nome che preferisci.</li><li>Dato che lo script <code>rmbyext</code> è installato da GitHub, <code>git</code> è una dipendenza di installazione. L'istruzione <code>RUN</code> nella riga 5, installa <code>git</code>, poi installa lo script <code>rmbyext</code> usando Git e pip. In seguito, si libera anche di <code>git</code>.</li><li>Infine, nella riga 9, l'istruzione <code>ENTRYPOINT</code> imposta lo script <code>rmbyext</code> come entry-point dell'immagine.</li></ul><p>In questo file, nella riga 9 c'è la magia che trasforma questa immagine apparentemente normale in un'immagine eseguibile. Adesso per fare il build dell'immagine puoi eseguire il seguente comando:</p><pre><code>docker image build --tag rmbyext .

# Sending build context to Docker daemon  2.048kB
# Step 1/4 : FROM python:3-alpine
# 3-alpine: Pulling from library/python
# 801bfaa63ef2: Already exists 
# 8723b2b92bec: Already exists 
# 4e07029ccd64: Already exists 
# 594990504179: Already exists 
# 140d7fec7322: Already exists 
# Digest: sha256:7492c1f615e3651629bd6c61777e9660caa3819cf3561a47d1d526dfeee02cf6
# Status: Downloaded newer image for python:3-alpine
#  ---&gt; d4d4f50f871a
# Step 2/4 : WORKDIR /zone
#  ---&gt; Running in 454374612a91
# Removing intermediate container 454374612a91
#  ---&gt; 7f7e49bc98d2
# Step 3/4 : RUN apk add --no-cache git &amp;&amp;     pip install git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext &amp;&amp;     apk del git
#  ---&gt; Running in 27e2e96dc95a
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 27e2e96dc95a
#  ---&gt; 3c7389432e36
# Step 4/4 : ENTRYPOINT [ "rmbyext" ]
#  ---&gt; Running in f239bbea1ca6
# Removing intermediate container f239bbea1ca6
#  ---&gt; 1746b0cedbc7
# Successfully built 1746b0cedbc7
# Successfully tagged rmbyext:latest

docker image ls

# REPOSITORY         TAG        IMAGE ID       CREATED         SIZE
# rmbyext            latest     1746b0cedbc7   4 minutes ago   50.9MB</code></pre><p>Qui non ho fornito nessun tag dopo il nome dell'immagine, quindi l'immagine è stata taggata di default come <code>latest</code>. Dovresti essere in grado di eseguire l'immagine come hai visto nella sezione precedente. Ricorda di fare riferimento al nome che hai impostato per l'immagine invece di <code>fhsinchy/rmbyext</code>.</p><h3 id="come-condividere-le-tue-immagini-docker-online"><strong>Come condividere le tue immagini Docker online</strong></h3><p>Ora che sai come creare delle immagini, è tempo di condividerle con il mondo. Condividere immagini online è semplice. Tutto ciò di cui hai bisogno è un account in un qualsiasi registro online. Io userò <a href="https://hub.docker.com/" rel="noopener noreferrer">Docker Hub</a>.</p><p>Vai sulla <a href="https://hub.docker.com/signup">pagina di registrazione</a> e crea un account gratuito, che ti permetterà di ospitare repository pubblici senza limiti e un repository privato.</p><p>Una volta creato l'account, dovrai accedere usando la CLI docker. Quindi apri il terminale ed esegui il seguente comando per farlo:</p><pre><code>docker login

# Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
# Username: fhsinchy
# Password: 
# WARNING! Your password will be stored unencrypted in /home/fhsinchy/.docker/config.json.
# Configure a credential helper to remove this warning. See
# https://docs.docker.com/engine/reference/commandline/login/#credentials-store
#
# Login Succeeded</code></pre><p>Ti verranno richiesti nome utente e password. Inserendoli correttamente, accederai al tuo account.</p><p>Per condividere un'immagine online, occorre che questa sia taggata. Hai imparato come taggare un'immagine in una sezione precedente. Tanto per rinfrescarti la memoria, la sintassi generica per l'opzione <code>--tag</code> o <code>-t</code> è la seguente:</p><pre><code>--tag &lt;repository immagine&gt;:&lt;tag immagine&gt;</code></pre><p>Come esempio, condividiamo online l'immagine <code>custom-nginx</code>. Per farlo, apri un nuovo terminale all'interno della directory del progetto <code>custom-nginx</code>.</p><p>Per condividere un'immagine online, devi taggarla seguendo la sintassi <code>&lt;username docker hub&gt;/&lt;nome immagine&gt;:&lt;tag immagine&gt;</code>. Il mio username è <code>fhsinchy</code>, quindi il comando sarà:</p><pre><code>docker image build --tag fhsinchy/custom-nginx:latest --file Dockerfile.built .

# Step 1/9 : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step 2/9 : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged fhsinchy/custom-nginx:latest</code></pre><p>In questo comando, <code>fhsinchy/custom-nginx</code> è il repository dell'immagine e <code>latest</code> è il tag. Il nome dell'immagine può essere qualsiasi cosa desideri e non può essere cambiato una volta che carichi l'immagine. Il tag può essere cambiato ogni volta che vuoi e solitamente riflette la versione del software o diversi tipi di build.</p><p>Prendi l'immagine <code>node</code> come esempio. L'immagine <code>node:lts</code> fa riferimento alla versione con supporto a lungo termine di Node.js, mentre la versione <code>node:lts-alpine</code> fa riferimento alla versione di Node.js per Alpine Linux, che è molto più leggera di quella normale.</p><p>Se non dai nessun tag all'immagine, sarà automaticamente taggata come <code>latest</code>. Ma ciò non vuol dire che il tag <code>latest</code> farà sempre riferimento all'ultima versione. Se per qualche ragione, usi esplicitamente il tag <code>latest</code> per una vecchia versione dell'immagine, Docker non farà nessuno sforzo extra per verificare che sia corretto.</p><p>Una volta che il build è stato effettuato, puoi caricare l'immagine eseguendo il seguente comando:</p><pre><code>docker image push &lt;repository immagine&gt;:&lt;tag immagine&gt;</code></pre><p>Quindi, in questo caso il comando sarà:</p><pre><code>docker image push fhsinchy/custom-nginx:latest

# The push refers to repository [docker.io/fhsinchy/custom-nginx]
# 4352b1b1d9f5: Pushed 
# a4518dd720bd: Pushed 
# 1d756dc4e694: Pushed 
# d7a7e2b6321a: Pushed 
# f6253634dc78: Mounted from library/ubuntu 
# 9069f84dbbe9: Mounted from library/ubuntu 
# bacd3af13903: Mounted from library/ubuntu 
# latest: digest: sha256:ffe93440256c9edb2ed67bf3bba3c204fec3a46a36ac53358899ce1a9eee497a size: 1788</code></pre><p>A seconda della dimensione dell'immagine, il caricamento potrebbe richiedere del tempo. Una volta terminato, dovresti essere in grado di trovare l'immagine nella pagina del tuo profilo sull'hub.</p><h2 id="come-containerizzare-una-app-javascript"><strong>Come containerizzare una app JavaScript</strong></h2><p>Ora che ti sei fatto un'idea di come creare immagini, è tempo di lavorare con qualcosa di più rilevante.</p><p>In questa sezione, lavorerai con il codice sorgente dell'immagine <a href="https://hub.docker.com/r/fhsinchy/hello-dock" rel="noopener noreferrer">fhsinchy/hello-dock</a> usata nella sezione precedente. Nel processo di containerizzazione di questa semplice applicazione, parleremo dei volumi e dei build multi-stadio, due dei concetti più importanti in Docker.</p><h3 id="come-scrivere-il-dockerfile-di-sviluppo"><strong>Come scrivere il Dockerfile di sviluppo</strong></h3><p>Per iniziare, apri la cartella in cui hai clonato il repository in dotazione con questo libro. Il codice per l'applicazione <code>hello-dock</code> si trova nella sotto-cartella con lo stesso nome.</p><p>Questo è un progetto JavaScript molto semplice realizzato dal progetto <a href="https://github.com/vitejs/vite" rel="noopener noreferrer">vitejs/vite</a>. Non preoccuparti, non hai bisogno di conoscere JavaScript o vite per affrontare questa sezione. Una comprensione base di <a href="https://nodejs.org/" rel="noopener noreferrer">Node.js</a> e <a href="https://www.npmjs.com/" rel="noopener noreferrer">npm</a> è sufficiente.</p><p>Proprio come ogni altro progetto delle sezioni precedenti, inizierai facendo un piano di come vuoi eseguire l'applicazione. Secondo me, il piano dovrebbe essere il seguente:</p><ul><li>Ottenere una buona immagine base per eseguire applicazioni JavaScript, come <a href="https://hub.docker.com/_/node" rel="noopener noreferrer">node</a>.</li><li>Definire la cartella di lavoro nell'immagine.</li><li>Copiare il file <code>package.json</code> nell'immagine.</li><li>Installare le dipendenze necessarie.</li><li>Copiare &nbsp;il resto dei file del progetto.</li><li>Avviare il server di sviluppo di <code>vite</code> eseguendo il comando <code>npm run dev</code>.</li></ul><p>Questo piano dovrebbe arrivare sempre dallo sviluppatore dell'applicazione che stai containerizzando. Se sei tu lo sviluppatore, allora dovresti avere una comprensione appropriata di come dovrebbe essere eseguita l'applicazione.</p><p>Se metti il piano sopra menzionato all'interno di <code>Dockerfile.dev</code>, il file dovrebbe avere questo aspetto:</p><pre><code class="language-dockerfile">FROM node:lts-alpine

EXPOSE 3000

USER node

RUN mkdir -p /home/node/app

WORKDIR /home/node/app

COPY ./package.json .
RUN npm install

COPY . .

CMD [ "npm", "run", "dev" ]</code></pre><p>La spiegazione di questo codice è la seguente:</p><ul><li>L'istruzione <code>FROM</code> imposta l'immagine ufficiale di Node.js come base, rendendo disponibili tutte le qualità di Node.js necessarie per eseguire qualsiasi applicazione JavaScript. Il tag <code>lts-alpine</code> indica che vuoi usare la variante Alpine, versione con supporto a lungo termine dell'immagine. I tag disponibili e la documentazione necessaria può essere trovata sulla pagina <a href="https://hub.docker.com/_/node" rel="noopener noreferrer">node</a>.</li><li>L'istruzione <code>USER</code> imposta l'utente predefinito per l'immagine come <code>node</code>. Di default Docker esegue i container come utente root. Ma secondo le buone pratiche di <a href="https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md" rel="noopener noreferrer">Docker e Node.js</a> questo può causare problemi di sicurezza. Quindi è meglio non farlo quando possibile. L'immagine di node ha un utente non-root chiamato <code>node</code>, che puoi impostare come utente di default usando l'istruzione <code>USER</code>.</li><li>L'istruzione <code>RUN mkdir -p /home/node/app</code> crea una cartella chiamata <code>app</code> all'interno della cartella home dell'utente <code>node</code>. La cartella home per qualsiasi utente non-root in Linux è solitamente <code>/home/&lt;nome utente&gt;</code> di default.</li><li>Poi l'istruzione <code>WORKDIR</code> imposta la cartella di lavoro predefinita con la nuova cartella <code>/home/node/app</code> appena creata. Di default, la cartella di lavoro di ogni immagini è la radice. Dato che non vuoi file non necessari sparsi in giro nella tua directory root, è bene cambiare la cartella di lavoro predefinita in qualcosa di più ragionevole come <code>/home/node/app</code> o quello che preferisci. Questa cartella di lavoro sarà applicabile a ogni istruzione <code>COPY</code>, <code>ADD</code>, <code>RUN</code> e <code>CMD</code> successiva.</li><li>L'istruzione <code>COPY</code> copia il file <code>package.json</code> che contiene informazioni riguardo a tutte le dipendenze necessarie per l'applicazione. L'istruzione <code>RUN</code> esegue il comando <code>npm install</code>, che è il comando predefinito per installare le dipendenze usando un file <code>package.json</code> nei progetti Node.js. Il punto <code>.</code> alla fine rappresenta la cartella di lavoro.</li><li>La seconda istruzione <code>COPY</code> copia il resto del contenuto della cartella corrente (<code>.</code>) del file system host nella cartella di lavoro (<code>.</code>) all'interno dell'immagine.</li><li>Infine, l'istruzione <code>CMD</code> imposta il comando predefinito per l'immagine, che è <code>npm run dev</code> scritto in forma <code>exec</code>.</li><li>Il server di sviluppo <code>vite</code> gira di default sulla porta <code>3000</code>, e aggiungere un comando <code>EXPOSE</code> sembrava una buona idea.</li></ul><p>Per fare il build dell'immagine da <code>Dockerfile.dev</code> puoi eseguire il seguente comando:</p><pre><code>docker image build --file Dockerfile.dev --tag hello-dock:dev .

# Step 1/7 : FROM node:lts
#  ---&gt; b90fa0d7cbd1
# Step 2/7 : EXPOSE 3000
#  ---&gt; Running in 722d639badc7
# Removing intermediate container 722d639badc7
#  ---&gt; e2a8aa88790e
# Step 3/7 : WORKDIR /app
#  ---&gt; Running in 998e254b4d22
# Removing intermediate container 998e254b4d22
#  ---&gt; 6bd4c42892a4
# Step 4/7 : COPY ./package.json .
#  ---&gt; 24fc5164a1dc
# Step 5/7 : RUN npm install
#  ---&gt; Running in 23b4de3f930b
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 23b4de3f930b
#  ---&gt; c17ecb19a210
# Step 6/7 : COPY . .
#  ---&gt; afb6d9a1bc76
# Step 7/7 : CMD [ "npm", "run", "dev" ]
#  ---&gt; Running in a7ff529c28fe
# Removing intermediate container a7ff529c28fe
#  ---&gt; 1792250adb79
# Successfully built 1792250adb79
# Successfully tagged hello-dock:dev</code></pre><p>Dato che il nome del file non è <code>Dockerfile</code>, devi passare esplicitamente il nome del file usando l'opzione <code>--file</code>. Un container può essere eseguito usando questa immagine con il seguente comando:</p><pre><code>docker container run \
    --rm \
    --detach \
    --publish 3000:3000 \
    --name hello-dock-dev \
    hello-dock:dev

# 21b9b1499d195d85e81f0e8bce08f43a64b63d589c5f15cbbd0b9c0cb07ae268</code></pre><p>Se visiti <code>http://127.0.0.1:3000</code> vedrai l'applicazione <code>hello-dock</code> in azione.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock-dev.png" class="kg-image" alt="hello-dock-dev" width="600" height="400" loading="lazy"></figure><p>Congratulazioni per aver eseguito la tua prima vera applicazione all'interno di un container. Il codice che hai appena scritto va bene, ma c'è un grande problema e qualche cosuccia che può essere migliorata. Iniziamo dal problema principale.</p><h3 id="come-lavorare-con-i-bind-mount-in-docker"><strong>Come lavorare con i bind mount in Docker</strong></h3><p>Se prima d'ora hai lavorato con qualsiasi framework front-end JavaScript, dovresti sapere che i server di sviluppo in questi framework hanno solitamente una funzionalità di ricaricamento rapido. Cioè, se fai un cambiamento al tuo codice, il server sarà ricaricato automaticamente riflettendo tutti i cambiamenti che hai fatto immediatamente.</p><p>Ma se fai delle modifiche al codice proprio adesso, non vedrai accadere nulla all'applicazione in esecuzione nel browser. Questo perché stai apportando dei cambiamenti al codice che hai nel tuo file system locale, ma l'applicazione che vedi nel browser risiede nel file system del container.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/local-vs-container-file-system.svg" class="kg-image" alt="local-vs-container-file-system" width="600" height="400" loading="lazy"></figure><p>Per risolvere questo problema, puoi fare di nuovo uso di un <a href="https://docs.docker.com/storage/bind-mounts/" rel="noopener noreferrer">bind mount</a>. Usando i bind mount, puoi montare facilmente una delle cartelle nel file system locale all'interno di un container. Invece di fare una copia del file system locale, il bind mount può fare riferimento al file system locale direttamente dall'interno del container.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/01/bind-mounts.svg" class="kg-image" alt="bind-mounts" width="600" height="400" loading="lazy"></figure><p>In questo modo, ogni cambiamento che fai alla tua sorgente locale verrà riflesso immediatamente all'interno del container, innescando la funzionalità di ricaricamento rapido del server di sviluppo <code>vite</code>. I cambiamenti apportati al file system nel container verranno riportati allo stesso modo nel file system locale.</p><p>Come hai già imparato nella sezione <a href="#come-lavorare-con-immagini-eseguibili">Come lavorare con immagini eseguibili</a>, i bind mount possono essere creati usando l'opzione <code>--volume</code> o <code>-v</code> per i comandi <code>container run</code> o <code>container start</code>. Come promemoria, ecco la sintassi generica:</p><pre><code>--volume &lt;percorso assoluto cartella file system locale&gt;:&lt;percorso assoluto cartella file system container&gt;:&lt;accesso read write&gt;</code></pre><p>Blocca il container <code>hello-dock-dev</code> precedentemente avviato e avviane uno nuovo eseguendo:</p><pre><code>docker container run \
    --rm \
    --publish 3000:3000 \
    --name hello-dock-dev \
    --volume $(pwd):/home/node/app \
    hello-dock:dev

# sh: 1: vite: not found
# npm ERR! code ELIFECYCLE
# npm ERR! syscall spawn
# npm ERR! file sh
# npm ERR! errno ENOENT
# npm ERR! hello-dock@0.0.0 dev: `vite`
# npm ERR! spawn ENOENT
# npm ERR!
# npm ERR! Failed at the hello-dock@0.0.0 dev script.
# npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
# npm WARN Local package.json exists, but node_modules missing, did you mean to install?</code></pre><p>Ho omesso l'opzione <code>--detach</code> per dimostrare un punto molto importante. Come puoi vedere, l'applicazione non viene eseguita adesso.</p><p>Questo perché l'uso di un volume risolve il problema del ricaricamento rapido, ma ne introduce un altro. Se hai dell'esperienza pregressa con Node.js, potresti sapere che le dipendenze di un progetto Node.js risiedono nella cartella <code>node_modules</code> nella radice del progetto.</p><p>Adesso che stai montando la radice del progetto sul tuo file system locale come un volume all'interno del container, il contenuto del container viene sostituito insieme alla cartella <code>node_modules</code>, in cui ci sono tutte le dipendenze. Ciò significa che il pacchetto <code>vite</code> è sparito.</p><h3 id="come-lavorare-con-volumi-anonimi-in-docker"><strong>Come lavorare con volumi anonimi in Docker</strong></h3><p>Questo problema può essere risolto usando un volume anonimo, che è identico a un bind mount, tranne per il fatto che non è necessario specificare la directory sorgente. La sintassi generica per creare un volume anonimo è la seguente:</p><pre><code>--volume &lt;percorso assoluto cartella file system container&gt;:&lt;accesso read write&gt;</code></pre><p>Quindi il comando finale per avviare il container <code>hello-dock</code> con entrambi i volumi è il seguente:</p><pre><code>docker container run \
    --rm \
    --detach \
    --publish 3000:3000 \
    --name hello-dock-dev \
    --volume $(pwd):/home/node/app \
    --volume /home/node/app/node_modules \
    hello-dock:dev

# 53d1cfdb3ef148eb6370e338749836160f75f076d0fbec3c2a9b059a8992de8b</code></pre><p>Docker prenderà l'intera cartella <code>node_modules</code> dall'interno del container, la metterà da parte in un'altra cartella gestita dal demone di Docker sul file system host e la monterà come <code>node_modules</code> all'interno del container.</p><h3 id="come-eseguire-dei-build-multi-stadio-in-docker"><strong>Come eseguire dei build multi-stadio in Docker</strong></h3><p>Finora in questa sezione, hai fatto il build di un'immagine per eseguire un'applicazione JavaScript in modalità di sviluppo. Quando vuoi fare il build dell'immagine in modalità di produzione, vengono fuori nuove sfide.</p><p>In modalità di sviluppo, il comando <code>npm run serve</code> avvia un server di sviluppo che serve l'applicazione all'utente. Non serve soltanto i file all'utente, ma fornisce anche la funzionalità di ricaricamento rapido.</p><p>In modalità di produzione, il comando <code>npm run build</code> compila tutto il codice JavaScript in file HTML statici, CSS e JavaScript. Per eseguire questi file, non c'è bisogno di node o di altre dipendenze di runtime. Tutto ciò di cui hai bisogno è un server come <code>nginx</code>, ad esempio.</p><p>Per creare un'immagine in cui l'applicazione viene eseguita in modalità di produzione, potresti seguire i seguenti passaggi:</p><ul><li>Usa <code>node</code> come immagine base e fai il build dell'applicazione.</li><li>Installa <code>nginx</code> all'interno dell'immagine di node e usala per servire i file statici.</li></ul><p>Questo approccio è assolutamente valido, ma il problema è che l'immagine di <code>node</code> è grande e la maggior parte di ciò che contiene non è necessaria per servire i file statici. Un approccio migliore per questo caso è il seguente:</p><ul><li>Usa <code>node</code> come immagine base e fai il build dell'applicazione.</li><li>Copia i file creati usando l'immagine di <code>node</code> in un'immagine di <code>nginx</code>.</li><li>Crea l'immagine finale sulla base di <code>nginx</code> e scarta tutta la roba correlata a <code>node</code>.</li></ul><p>In questo modo, l'immagine contiene solo i file che sono necessari e diventa molto gestibile.</p><p>Questo approccio è un build multi-stadio. Per eseguirlo, crea un nuovo <code>Dockerfile</code> all'interno della directory del progetto <code>hello-dock</code> e inserisci il seguente contenuto:</p><pre><code class="language-dockerfile">FROM node:lts-alpine as builder

WORKDIR /app

COPY ./package.json ./
RUN npm install

COPY . .
RUN npm run build

FROM nginx:stable-alpine

EXPOSE 80

COPY --from=builder /app/dist /usr/share/nginx/html</code></pre><p>Come puoi vedere, <code>Dockerfile</code> somiglia molto ai precedenti con qualche particolarità. La spiegazione per questo file è la seguente:</p><ul><li>Nella riga 1 inizia il primo stadio di build usando <code>node:lts-alpine</code> come immagine base. La sintassi <code>as builder</code> assegna a questo stadio un nome con cui farvi riferimento in seguito.</li><li>Dalla riga 3 alla 9, cose standard che hai già visto molte volte. Il comando <code>RUN npm run build</code> compila l'intera applicazione e la mette da parte nella cartella <code>/app/dist</code> dove <code>/app</code> è la cartella di lavoro e <code>/dist</code> è la cartella di output predefinita per le applicazioni <code>vite</code>.</li><li>Nella riga 11 inizia il secondo stadio del build usando <code>nginx:stable-alpine</code> come immagine base.</li><li>Il server NGINX gira sulla porta 80 di default, di qui la riga <code>EXPOSE 80</code>.</li><li>L'ultima riga è un'istruzione <code>COPY</code>. La parte <code>--from=builder</code> indica che vuoi copiare dei file dallo stadio <code>builder</code>. Oltre questo, si tratta di un'istruzione copy standard dove <code>/app/dist</code> è la sorgente e <code>/usr/share/nginx/html</code> è la destinazione. La destinazione usata qui è il percorso predefinito per NGINX, in modo che ogni file statico che metti al suo interno sarà servito automaticamente.</li></ul><p>Come puoi vedere, l'immagine risultante è una base <code>nginx</code> contenente solo i file necessari per eseguire l'applicazione. Per svolgere il build di quest'immagine esegui il seguente comando:</p><pre><code>docker image build --tag hello-dock:prod .

# Step 1/9 : FROM node:lts-alpine as builder
#  ---&gt; 72aaced1868f
# Step 2/9 : WORKDIR /app
#  ---&gt; Running in e361c5c866dd
# Removing intermediate container e361c5c866dd
#  ---&gt; 241b4b97b34c
# Step 3/9 : COPY ./package.json ./
#  ---&gt; 6c594c5d2300
# Step 4/9 : RUN npm install
#  ---&gt; Running in 6dfabf0ee9f8
# npm WARN deprecated fsevents@2.1.3: Please update to v 2.2.x
#
# &gt; esbuild@0.8.29 postinstall /app/node_modules/esbuild
# &gt; node install.js
#
# npm notice created a lockfile as package-lock.json. You should commit this file.
# npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.1.2 (node_modules/chokidar/node_modules/fsevents):
# npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
# npm WARN hello-dock@0.0.0 No description
# npm WARN hello-dock@0.0.0 No repository field.
# npm WARN hello-dock@0.0.0 No license field.
#
# added 327 packages from 301 contributors and audited 329 packages in 35.971s
#
# 26 packages are looking for funding
#   run `npm fund` for details
#
# found 0 vulnerabilities
#
# Removing intermediate container 6dfabf0ee9f8
#  ---&gt; 21fd1b065314
# Step 5/9 : COPY . .
#  ---&gt; 43243f95bff7
# Step 6/9 : RUN npm run build
#  ---&gt; Running in 4d918cf18584
#
# &gt; hello-dock@0.0.0 build /app
# &gt; vite build
#
# - Building production bundle...
#
# [write] dist/index.html 0.39kb, brotli: 0.15kb
# [write] dist/_assets/docker-handbook-github.3adb4865.webp 12.32kb
# [write] dist/_assets/index.eabcae90.js 42.56kb, brotli: 15.40kb
# [write] dist/_assets/style.0637ccc5.css 0.16kb, brotli: 0.10kb
# - Building production bundle...
#
# Build completed in 1.71s.
#
# Removing intermediate container 4d918cf18584
#  ---&gt; 187fb3e82d0d
# Step 7/9 : EXPOSE 80
#  ---&gt; Running in b3aab5cf5975
# Removing intermediate container b3aab5cf5975
#  ---&gt; d6fcc058cfda
# Step 8/9 : FROM nginx:stable-alpine
# stable: Pulling from library/nginx
# 6ec7b7d162b2: Already exists 
# 43876acb2da3: Pull complete 
# 7a79edd1e27b: Pull complete 
# eea03077c87e: Pull complete 
# eba7631b45c5: Pull complete 
# Digest: sha256:2eea9f5d6fff078ad6cc6c961ab11b8314efd91fb8480b5d054c7057a619e0c3
# Status: Downloaded newer image for nginx:stable
#  ---&gt; 05f64a802c26
# Step 9/9 : COPY --from=builder /app/dist /usr/share/nginx/html
#  ---&gt; 8c6dfc34a10d
# Successfully built 8c6dfc34a10d
# Successfully tagged hello-dock:prod</code></pre><p>Una volta completato il build, puoi eseguire un nuovo container con il comando:</p><pre><code>docker container run \
    --rm \
    --detach \
    --name hello-dock-prod \
    --publish 8080:80 \
    hello-dock:prod

# 224aaba432bb09aca518fdd0365875895c2f5121eb668b2e7b2d5a99c019b953</code></pre><p>L'applicazione in esecuzione dovrebbe essere disponibile su <code>http://127.0.0.1:8080</code>:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" class="kg-image" alt="hello-dock" width="600" height="400" loading="lazy"></figure><p>Qui puoi vedere la mia applicazione <code>hello-dock</code> in tutta la sua gloria. I build multi-stadio possono essere molto utili per applicazioni estese con molte dipendenze. Se configurate correttamente, le immagini con build multi-stadio possono essere davvero ottimizzate e compatte.</p><h3 id="come-ignorare-i-file-non-necessari"><strong>Come ignorare i file non necessari</strong></h3><p>Se hai lavorato a dei progetti con <code>git</code>, saprai dei file <code>.gitignore</code>. Contengono una lista di file e cartelle da escludere dal repository.</p><p>Beh, Docker possiede un concetto simile. Il file <code>.dockerignore</code> contiene una lista di file e cartelle da escludere dal build di un'immagine. Puoi trovare un file <code>.dockerignore</code> già creato nella cartella <code>hello-dock</code>.</p><pre><code class="language-undefined">.git
*Dockerfile*
*docker-compose*
node_modules</code></pre><p>Il file <code>.dockerignore</code> deve essere nel contesto di build. I file e le cartelle menzionate qui saranno ignorati dall'istruzione <code>COPY</code>. Ma se usi un bind mount, il file <code>.dockerignore</code> non avrà effetto. Ho aggiunto dei file <code>.dockerignore</code> dove necessario nel repository del progetto.</p><h2 id="manipolazione-base-delle-reti-in-docker"><strong>Manipolazione base delle reti in Docker</strong></h2><p>Finora in questo manuale, hai lavorato su progetti con container singoli. Ma in realtà, la maggior parte dei progetti con cui avrai a che fare nella vita reale avrà più di un container. E onestamente, lavorare con un gruppo di container può essere un po' difficile se non afferri le sfumature dell'isolamento dei container.</p><p>In questa sezione, acquisirai familiarità con il networking di base con Docker e lavorerai direttamente su un piccolo progetto multi-container.</p><p>Hai già imparato nella sezione precedente che i container sono ambienti isolati. Ora considera una situazione in cui hai una applicazione <code>notes-api</code> alimentata da <a href="https://expressjs.com/" rel="noopener noreferrer">Express.js</a> e un database <a href="https://www.postgresql.org/" rel="noopener noreferrer">PostgreSQL</a> in esecuzione in due container separati.</p><p>Questi due container sono completamente isolati tra di loro e ognuno ignora l'esistenza dell'altro. <strong>Come puoi connetterli?</strong> Sembra una bella sfida.</p><p>Puoi pensare a due possibili soluzioni a questo problema:‌</p><ul><li>Accedere al server del database usando una porta esposta.</li><li>Accedere al server del database usando il suo indirizzo IP e la porta predefinita.</li></ul><p>La prima implica di esporre una porta dal container <code>postgres</code> e <code>notes-api</code> si connetterà attraverso di essa. Supponi che la porta esposta dal container <code>postgres</code> sia 5432. Se provi a connetterti a <code>127.0.0.1:5432</code> dal container <code>notes-api</code>, scoprirai che <code>notes-api</code> non è in grado di trovare il server del database.</p><p>La ragione è che quando dici <code>127.0.0.1</code> all'interno del container <code>notes-api</code>, fai riferimento al <code>localhost</code> soltanto di quel container. Il server <code>postgres</code> non esiste lì. Come risultato, l'applicazione <code>notes-api</code> fallisce la connessione.</p><p>La seconda soluzione che puoi escogitare è trovare l'esatto indirizzo IP del container <code>postgres</code> usando il comando <code>container inspect</code> e usarlo con la porta. Assumendo che il nome del container <code>postgres</code> sia <code>notes-api-db-server</code>, puoi ottenere facilmente l'indirizzo IP eseguendo il seguente comando:</p><pre><code>docker container inspect --format='{{range .NetworkSettings.Networks}} {{.IPAddress}} {{end}}' notes-api-db-server

#  172.17.0.2</code></pre><p>Posto che la porta di default per <code>postgres</code> è <code>5432</code>, puoi accedere al server del database molto facilmente connettendoti a <code>172.17.0.2:5432</code> dal container <code>notes-api</code>.</p><p>Anche in questo approccio ci sono dei problemi. Usare l'indirizzo IP per fare riferimento a un container non è raccomandato. Inoltre, se il container viene distrutto e ricreato, l'indirizzo IP può cambiare. Tenere traccia dei cambiamenti dell'indirizzo IP può essere un po' caotico.</p><p>Ora che ho liquidato le possibili risposte sbagliate alla domanda originale, la risposta corretta è che <strong>li connetti mettendoli sotto una rete bridge definita dall'utente</strong>.</p><h3 id="basi-delle-reti-docker"><strong>Basi delle reti Docker</strong></h3><p>Una rete (network) Docker è un altro oggetto logico come una container o un'immagine. Proprio come per gli altri due, esiste una pletora di comandi nel gruppo <code>docker network</code> per manipolare i network.</p><p>Per elencare le reti nel tuo sistema, esegui il seguente comando:</p><pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# 124dccee067f   host      host      local
# 506e3822bf1f   none      null      local</code></pre><p>Dovresti vedere 3 reti nel tuo sistema. Ora guarda la colonna <code>DRIVER</code> della tabella. Questi driver possono essere interpretati come il tipo di rete.</p><p>Di default, Docker ha cinque driver di rete:</p><ul><li><code>bridge</code> - Il driver di rete predefinito in Docker. Può essere usato quando più container sono in esecuzione in modalità standard e devono comunicare tra di loro.</li><li><code>host</code> - Rimuove completamente l'isolamento della rete. Ogni container in esecuzione sotto una rete <code>host</code> è praticamente collegato alla rete del sistema host.</li><li><code>none</code> - Questo driver disabilita del tutto la rete per i container. Non ho ancora trovato dei casi in cui utilizzarlo.</li><li><code>overlay</code> - Usato per connettere più demoni Docker tra computer; fuori dallo scopo di questo libro.</li><li><code>macvlan</code> - Permette di assegnare indirizzi MAC ai container, facendoli funzionare come dispositivi fisici in una rete.</li></ul><p>Esistono anche plugin di terze parti che consentono di integrare Docker con stack di rete specializzati. Tra i cinque menzionati qui sopra, in questo manuale lavorerai soltanto con il driver di rete <code>bridge</code>.</p><h3 id="come-creare-un-bridge-in-docker"><strong>Come creare un bridge in Docker</strong></h3><p>Prima di iniziare a creare il tuo bridge, vorrei spendere un po' di tempo per discutere della rete bridge predefinita. Iniziamo elencando tutte le reti del sistema:</p><pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# 124dccee067f   host      host      local
# 506e3822bf1f   none      null      local</code></pre><p>Come puoi vedere, Docker possiede una rete bridge di default, denominata <code>bridge</code>. Ogni container che esegui si collegherà automaticamente a questa rete bridge:</p><pre><code>docker container run --rm --detach --name hello-dock --publish 8080:80 fhsinchy/hello-dock
# a37f723dad3ae793ce40f97eb6bb236761baa92d72a2c27c24fc7fda0756657d

docker network inspect --format='{{range .Containers}}{{.Name}}{{end}}' bridge
# hello-dock</code></pre><p>I container collegati alla rete bridge predefinita possono comunicare tra loro usando gli indirizzi IP, di cui ho sconsigliato l'uso nella sezione precedente.</p><p>Tuttavia, un bridge definito da un utente ha delle funzionalità extra rispetto a quello di default. Secondo la <a href="https://docs.docker.com/network/bridge/#differences-between-user-defined-bridges-and-the-default-bridge">documentazione ufficiale</a> su questo argomento, alcune funzionalità aggiuntive importanti sono le seguenti:</p><ul><li><strong>I bridge definiti da un utente offrono la risoluzione DNS automatica tra container<strong>:</strong></strong> ciò vuol dire che i container collegati alla stessa rete possono comunicare tra loro usando il nome del container. Dunque, se hai due container chiamati <code>notes-api</code> e <code>notes-db</code> il container dell'API sarà in grado di connettersi al container del database usando il nome <code>notes-db</code>.</li><li><strong>I bridge definiti da un utente offrono un migliore isolamento<strong>: </strong></strong>tutti i container sono collegati alla rete bridge di default, il che può causare dei conflitti tra di loro. Collegare i container a un bridge definito da un utente può garantire un isolamento migliore.</li><li><strong>I container possono essere collegati e scollegati al volo da reti definiti da un utente<strong>:</strong></strong> durante la vita di un container, puoi connetterlo e disconnetterlo da reti definite da un utente molto semplicemente. Per rimuovere il container dalla rete bridge predefinita, devi fermare il container e ricrearlo con diverse opzioni di rete.</li></ul><p>Ora che hai imparato abbastanza sulle reti definite da un utente, è tempo di crearne una. Una rete può essere creata usando il comando <code>network create</code>, con la seguente sintassi generica:</p><pre><code>docker network create &lt;nome rete&gt;</code></pre><p>Per creare una rete con il nome <code>skynet</code> esegui il seguente comando:</p><pre><code>docker network create skynet

# 7bd5f351aa892ac6ec15fed8619fc3bbb95a7dcdd58980c28304627c8f7eb070

docker network ls

# NETWORK ID     NAME     DRIVER    SCOPE
# be0cab667c4b   bridge   bridge    local
# 124dccee067f   host     host      local
# 506e3822bf1f   none     null      local
# 7bd5f351aa89   skynet   bridge    local</code></pre><p>Come puoi vedere, è stata creata una nuova rete con il nome dato. Nessun container è attualmente collegato a questa rete. Nella prossima sezione, imparerai come collegare i container a una rete.</p><h3 id="come-collegare-un-container-a-una-rete-in-docker"><strong>Come collegare un container a una rete in Docker</strong></h3><p>Esistono principalmente due modi per collegare un container a una rete. Il primo modo è usare il comando <code>network connect</code>, la cui sintassi generica è:</p><pre><code>docker network connect &lt;identificatore rete&gt; &lt;identificatore container&gt;</code></pre><p>Per connettere il container <code>hello-dock</code> alla rete <code>skynet</code>, puoi eseguire il seguente comando:</p><pre><code>docker network connect skynet hello-dock

docker network inspect --format='{{range .Containers}} {{.Name}} {{end}}' skynet

#  hello-dock

docker network inspect --format='{{range .Containers}} {{.Name}} {{end}}' bridge

#  hello-dock</code></pre><p>Come puoi vedere dagli output dei due comandi <code>network inspect</code>, il container <code>hello-dock</code> adesso è collegato sia alla rete <code>skynet</code> che alla rete <code>bridge</code> predefinita.</p><p>Il secondo modo di collegare un container a una rete è usare l'opzione <code>--network</code> per i comandi <code>container run</code> o <code>container create</code>. La sintassi generica dell'opzione è la seguente:</p><pre><code>--network &lt;identificatore rete&gt;</code></pre><p>Per eseguire un altro container <code>hello-dock</code> collegato alla stessa rete, puoi eseguire il seguente comando:</p><pre><code>docker container run --network skynet --rm --name alpine-box -it alpine sh

# lands you into alpine linux shell

/ # ping hello-dock

# PING hello-dock (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.191 ms
# 64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.103 ms
# 64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.139 ms
# 64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.142 ms
# 64 bytes from 172.18.0.2: seq=4 ttl=64 time=0.146 ms
# 64 bytes from 172.18.0.2: seq=5 ttl=64 time=0.095 ms
# 64 bytes from 172.18.0.2: seq=6 ttl=64 time=0.181 ms
# 64 bytes from 172.18.0.2: seq=7 ttl=64 time=0.138 ms
# 64 bytes from 172.18.0.2: seq=8 ttl=64 time=0.158 ms
# 64 bytes from 172.18.0.2: seq=9 ttl=64 time=0.137 ms
# 64 bytes from 172.18.0.2: seq=10 ttl=64 time=0.145 ms
# 64 bytes from 172.18.0.2: seq=11 ttl=64 time=0.138 ms
# 64 bytes from 172.18.0.2: seq=12 ttl=64 time=0.085 ms

--- hello-dock ping statistics ---
13 packets transmitted, 13 packets received, 0% packet loss
round-trip min/avg/max = 0.085/0.138/0.191 ms</code></pre><p>Come puoi vedere, eseguire <code>ping hello-dock</code> dall'interno del container <code>alpine-box</code> funziona perché entrambi i contenitori sono sotto la stessa rete bridge definita da un utente e la risoluzione DNS automatica sta funzionando.</p><p>Però tieni a mente che affinché la risoluzione DNS automatica funzioni, devi assegnare nomi personalizzati ai contenitori. Usando i nomi casuali generati automaticamente non funzionerà.</p><h3 id="come-scollegare-dei-container-da-una-rete-in-docker"><strong>Come scollegare dei container da una rete in Docker</strong></h3><p>Nella sezione precedente hai imparato come collegare i container a una rete. In questa sezione imparerai come scollegarli.</p><p>Per compiere questa azione puoi usare il comando <code>network disconnect</code>. La sintassi generica è la seguente:</p><pre><code>docker network disconnect &lt;identificatore rete&gt; &lt;identificatore container&gt;</code></pre><p>Per scollegare il container <code>hello-dock</code> dalla rete <code>skynet</code>, puoi eseguire il seguente comando:</p><pre><code>docker network disconnect skynet hello-dock</code></pre><p>Proprio come il comando <code>network connect</code>, anche il comando <code>network disconnect</code> non dà output.</p><h3 id="come-rimuovere-una-rete-in-docker"><strong>Come rimuovere una rete in Docker</strong></h3><p>Proprio come gli altri oggetti logici in Docker, le reti possono essere rimosse usando il comando <code>network rm</code>. La sintassi generica è:</p><pre><code>docker network rm &lt;identificatore rete&gt;</code></pre><p>Per rimuovere la rete <code>skynet</code> dal tuo sistema, puoi eseguire il seguente comando:</p><pre><code>docker network rm skynet</code></pre><p>Puoi anche usare il comando <code>network prune</code> per rimuovere ogni rete inutilizzata dal tuo sistema. Il comando ha anche le opzioni <code>-f</code> o <code>--force</code> e <code>-a</code> o <code>--all</code>.</p><h2 id="come-containerizzare-un-applicazione-javascript-multi-container"><strong>Come containerizzare un'applicazione JavaScript multi-container</strong></h2><p>Ora che hai imparato abbastanza sulle reti in Docker, in questa sezione imparerai a containerizzare un progetto multi-container a vero e proprio. Il progetto su cui lavorerai è una semplice <code>notes-api</code> alimentata da Express.js e PostgreSQL.</p><p>In questo progetto, ci sono due contenitori che dovrai connettere usando una rete. Oltre questo, imparerai anche concetti come le variabili di ambiente e i volumi. Senza ulteriori indugi, andiamo subito al dunque. </p><h3 id="come-eseguire-il-server-del-database"><strong>Come eseguire il server del database</strong></h3><p>Il server del database in questo progetto è un semplice server PostgreSQL e utilizza l'immagine ufficiale di <a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer">postgres</a>.</p><p>Secondo la documentazione ufficiale, per eseguire un container con questa immagine, devi fornire la variabile di ambiente <code>POSTGRES_PASSWORD</code>. In aggiunta, darò anche un nome per il database di default usando la variabile di ambiente <code>POSTGRES_DB</code>. PostgreSQL di default è in ascolto sulla porta <code>5432</code>, quindi devi pubblicare anche quella.</p><p>Per eseguire il server del database puoi eseguire il seguente comando:</p><pre><code>docker container run \
    --detach \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    postgres:12

# a7b287d34d96c8e81a63949c57b83d7c1d71b5660c87f5172f074bd1606196dc

docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS              PORTS      NAMES
# a7b287d34d96   postgres:12   "docker-entrypoint.s…"   About a minute ago   Up About a minute   5432/tcp   notes-db</code></pre><p>L'opzione <code>--env</code> per i comandi <code>container run</code> e <code>container create</code> può essere usata per fornire le variabili di ambiente a un container. Come puoi vedere, il container del database è stato creato con successo ed è in esecuzione.</p><p>Sebbene il container sia in esecuzione, c'è un piccolo problema. Database come PostgreSQL, MongoDB e MySQL mantengono i propri dati in una cartella. PostgreSQL usa la cartella <code>/var/lib/postgresql/data</code> all'interno del container per mantenere i dati.</p><p>E se il container venisse distrutto per qualche ragione? Perderesti tutti i dati. Per risolvere questo problema può essere usato un volume con un nome.</p><h3 id="come-lavorare-con-i-volumi-in-docker"><strong>Come lavorare con i volumi in Docker</strong></h3><p>Precedentemente hai usato bind mount e volumi anonimi. Un volume con un nome è molto simile a un volume anonimo, eccetto che puoi farvi riferimento usando il suo nome.</p><p>I volumi sono anche oggetti logici in Docker e possono essere manipolati usando la riga di comando. Il comando <code>volume create</code> può essere usato per creare un volume con un nome.</p><p>La sintassi generica del comando è la seguente:</p><pre><code>docker volume create &lt;nome volume&gt;</code></pre><p>Per creare un volume chiamato <code>notes-db-data</code> puoi eseguire il seguente comando:</p><pre><code>docker volume create notes-db-data

# notes-db-data

docker volume ls

# DRIVER    VOLUME NAME
# local     notes-db-data</code></pre><p>Questo volume può essere montato su <code>/var/lib/postgresql/data</code> all'interno del container <code>notes-db</code>. Per farlo, blocca e rimuovi il container <code>notes-db</code>:</p><pre><code>docker container stop notes-db

# notes-db

docker container rm notes-db

# notes-db</code></pre><p>Adesso esegui un nuovo container e assegna il volume usando l'opzione <code>--volume</code> o <code>-v</code>.</p><pre><code>docker container run \
    --detach \
    --volume notes-db-data:/var/lib/postgresql/data \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    postgres:12

# 37755e86d62794ed3e67c19d0cd1eba431e26ab56099b92a3456908c1d346791</code></pre><p>Adesso analizza il container <code>notes-db</code> per assicurarti che il montaggio sia &nbsp;avvenuto con successo:</p><pre><code>docker container inspect --format='{{range .Mounts}} {{ .Name }} {{end}}' notes-db

#  notes-db-data</code></pre><p>Ora i dati saranno conservati al sicuro all'interno del volume <code>notes-db-data</code> e potranno essere riusati in futuro. È possibile usare anche un bind mount ma in questi casi preferisco un volume.</p><h3 id="come-accedere-ai-log-da-un-container-in-docker"><strong>Come accedere ai log da un container in Docker</strong></h3><p>Per vedere i log da un container, puoi usare il comando <code>container logs</code>. La sintassi generica del comando è la seguente:</p><pre><code>docker container logs &lt;identificatore container&gt;</code></pre><p>Per accedere ai log dal container <code>notes-db</code>, puoi eseguire il seguente comando:</p><pre><code>docker container logs notes-db

# The files belonging to this database system will be owned by user "postgres".
# This user must also own the server process.

# The database cluster will be initialized with locale "en_US.utf8".
# The default database encoding has accordingly been set to "UTF8".
# The default text search configuration will be set to "english".
#
# Data page checksums are disabled.
#
# fixing permissions on existing directory /var/lib/postgresql/data ... ok
# creating subdirectories ... ok
# selecting dynamic shared memory implementation ... posix
# selecting default max_connections ... 100
# selecting default shared_buffers ... 128MB
# selecting default time zone ... Etc/UTC
# creating configuration files ... ok
# running bootstrap script ... ok
# performing post-bootstrap initialization ... ok
# syncing data to disk ... ok
#
#
# Success. You can now start the database server using:
#
#     pg_ctl -D /var/lib/postgresql/data -l logfile start
#
# initdb: warning: enabling "trust" authentication for local connections
# You can change this by editing pg_hba.conf or using the option -A, or
# --auth-local and --auth-host, the next time you run initdb.
# waiting for server to start....2021-01-25 13:39:21.613 UTC [47] LOG:  starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:21.621 UTC [47] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:21.675 UTC [48] LOG:  database system was shut down at 2021-01-25 13:39:21 UTC
# 2021-01-25 13:39:21.685 UTC [47] LOG:  database system is ready to accept connections
#  done
# server started
# CREATE DATABASE
#
#
# /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
#
# 2021-01-25 13:39:22.008 UTC [47] LOG:  received fast shutdown request
# waiting for server to shut down....2021-01-25 13:39:22.015 UTC [47] LOG:  aborting any active transactions
# 2021-01-25 13:39:22.017 UTC [47] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
# 2021-01-25 13:39:22.017 UTC [49] LOG:  shutting down
# 2021-01-25 13:39:22.056 UTC [47] LOG:  database system is shut down
#  done
# server stopped
#
# PostgreSQL init process complete; ready for start up.
#
# 2021-01-25 13:39:22.135 UTC [1] LOG:  starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv6 address "::", port 5432
# 2021-01-25 13:39:22.147 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:22.177 UTC [75] LOG:  database system was shut down at 2021-01-25 13:39:22 UTC
# 2021-01-25 13:39:22.190 UTC [1] LOG:  database system is ready to accept connections</code></pre><p>Come si evince dal testo della riga 57, il database è configurato e pronto per accettare connessioni esterne. Esiste anche l'opzione <code>--follow</code> o <code>-f</code> per questo comando, che ti permette di collegare la console all'output dei log e ottenere un flusso di testo continuo.</p><h3 id="come-creare-una-rete-e-collegare-il-server-del-database-in-docker"><strong>Come creare una rete e collegare il server del database in Docker</strong></h3><p>Come hai già imparato nella sezione precedente, i container devono essere collegati a una rete bridge definita da un utente per poter comunicare tra loro usando i nomi dei container. A questo scopo, nel tuo sistema crea una rete chiamata <code>notes-api-network</code>:</p><pre><code>docker network create notes-api-network</code></pre><p>Adesso collega il container <code>notes-db</code> a questa rete, eseguendo il seguente comando:</p><pre><code>docker network connect notes-api-network notes-db</code></pre><h3 id="come-scrivere-il-dockerfile"><strong>Come scrivere il Dockerfile</strong></h3><p>Vai nella cartella in cui hai clonato il codice del progetto. Al suo interno, vai nella cartella <code>notes-api/api</code> e crea un nuovo <code>Dockerfile</code>. Inserisci nel file il seguente codice:</p><pre><code># stage one
FROM node:lts-alpine as builder

# install dependencies for node-gyp
RUN apk add --no-cache python make g++

WORKDIR /app

COPY ./package.json .
RUN npm install --only=prod

# stage two
FROM node:lts-alpine

EXPOSE 3000
ENV NODE_ENV=production

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY . .
COPY --from=builder /app/node_modules  /home/node/app/node_modules

CMD [ "node", "bin/www" ]</code></pre><p>Questo è un build multi-stadio. Il primo stadio viene usato per il build e l'installazione delle dipendenze usando <code>node-gyp</code> e il secondo stadio è per eseguire l'applicazione. Analizziamo brevemente i passaggi:</p><ul><li>Lo stadio 1 utilizza <code>node:lts-alpine</code> come base e <code>builder</code> come nome dello stadio.</li><li>Nella riga 5, installiamo <code>python</code>, <code>make</code> e <code>g++</code>. Lo strumento <code>node-gyp</code> richiede questi tre pacchetti per essere eseguito.</li><li>Nella riga 7, impostiamo la cartella <code>/app</code> come <code>WORKDIR</code> .</li><li>Nelle righe 9 e 10, copiamo il file <code>package.json</code> in <code>WORKDIR</code> e installiamo tutte le dipendenze.</li><li>Anche lo stadio 2 fa uso di <code>node-lts:alpine</code> come base.</li><li>Nella riga 16, impostiamo la variabile di ambiente <code>NODE_ENV</code> su <code>production</code>. È importante affinché l'API sia eseguita in modo appropriato.</li><li>Dalla riga 18 alla 20, impostiamo utente predefinito su <code>node</code>, creiamo la cartella <code>/home/node/app</code> e la impostiamo come <code>WORKDIR</code>.</li><li>Nella riga 22, copiamo tutti i file del progetto e nella riga 23 copiamo la cartella <code>node_modules</code> dallo stadio <code>builder</code>. Questa cartella contiene tutte le dipendenze necessarie per eseguire l'applicazione.</li><li>Nella riga 25, definiamo il comando di default.</li></ul><p>Per eseguire il build di un'immagine da questo <code>Dockerfile</code>, puoi usare il seguente comando:</p><pre><code>docker image build --tag notes-api .

# Sending build context to Docker daemon  37.38kB
# Step 1/14 : FROM node:lts-alpine as builder
#  ---&gt; 471e8b4eb0b2
# Step 2/14 : RUN apk add --no-cache python make g++
#  ---&gt; Running in 5f20a0ecc04b
# fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
# fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
# (1/21) Installing binutils (2.33.1-r0)
# (2/21) Installing gmp (6.1.2-r1)
# (3/21) Installing isl (0.18-r0)
# (4/21) Installing libgomp (9.3.0-r0)
# (5/21) Installing libatomic (9.3.0-r0)
# (6/21) Installing mpfr4 (4.0.2-r1)
# (7/21) Installing mpc1 (1.1.0-r1)
# (8/21) Installing gcc (9.3.0-r0)
# (9/21) Installing musl-dev (1.1.24-r3)
# (10/21) Installing libc-dev (0.7.2-r0)
# (11/21) Installing g++ (9.3.0-r0)
# (12/21) Installing make (4.2.1-r2)
# (13/21) Installing libbz2 (1.0.8-r1)
# (14/21) Installing expat (2.2.9-r1)
# (15/21) Installing libffi (3.2.1-r6)
# (16/21) Installing gdbm (1.13-r1)
# (17/21) Installing ncurses-terminfo-base (6.1_p20200118-r4)
# (18/21) Installing ncurses-libs (6.1_p20200118-r4)
# (19/21) Installing readline (8.0.1-r0)
# (20/21) Installing sqlite-libs (3.30.1-r2)
# (21/21) Installing python2 (2.7.18-r0)
# Executing busybox-1.31.1-r9.trigger
# OK: 212 MiB in 37 packages
# Removing intermediate container 5f20a0ecc04b
#  ---&gt; 637ca797d709
# Step 3/14 : WORKDIR /app
#  ---&gt; Running in 846361b57599
# Removing intermediate container 846361b57599
#  ---&gt; 3d58a482896e
# Step 4/14 : COPY ./package.json .
#  ---&gt; 11b387794039
# Step 5/14 : RUN npm install --only=prod
#  ---&gt; Running in 2e27e33f935d
#  added 269 packages from 220 contributors and audited 1137 packages in 140.322s
#
# 4 packages are looking for funding
#   run `npm fund` for details
#
# found 0 vulnerabilities
#
# Removing intermediate container 2e27e33f935d
#  ---&gt; eb7cb2cb0b20
# Step 6/14 : FROM node:lts-alpine
#  ---&gt; 471e8b4eb0b2
# Step 7/14 : EXPOSE 3000
#  ---&gt; Running in 4ea24f871747
# Removing intermediate container 4ea24f871747
#  ---&gt; 1f0206f2f050
# Step 8/14 : ENV NODE_ENV=production
#  ---&gt; Running in 5d40d6ac3b7e
# Removing intermediate container 5d40d6ac3b7e
#  ---&gt; 31f62da17929
# Step 9/14 : USER node
#  ---&gt; Running in 0963e1fb19a0
# Removing intermediate container 0963e1fb19a0
#  ---&gt; 0f4045152b1c
# Step 10/14 : RUN mkdir -p /home/node/app
#  ---&gt; Running in 0ac591b3adbd
# Removing intermediate container 0ac591b3adbd
#  ---&gt; 5908373dfc75
# Step 11/14 : WORKDIR /home/node/app
#  ---&gt; Running in 55253b62ff57
# Removing intermediate container 55253b62ff57
#  ---&gt; 2883cdb7c77a
# Step 12/14 : COPY . .
#  ---&gt; 8e60893a7142
# Step 13/14 : COPY --from=builder /app/node_modules  /home/node/app/node_modules
#  ---&gt; 27a85faa4342
# Step 14/14 : CMD [ "node", "bin/www" ]
#  ---&gt; Running in 349c8ca6dd3e
# Removing intermediate container 349c8ca6dd3e
#  ---&gt; 9ea100571585
# Successfully built 9ea100571585
# Successfully tagged notes-api:latest</code></pre><p>Prima di eseguire un container usando questa immagine, assicurati che il container del database sia in esecuzione e collegato a <code>notes-api-network</code>.</p><pre><code>docker container inspect notes-db

# [
#     {
#         ...
#         "State": {
#             "Status": "running",
#             "Running": true,
#             "Paused": false,
#             "Restarting": false,
#             "OOMKilled": false,
#             "Dead": false,
#             "Pid": 11521,
#             "ExitCode": 0,
#             "Error": "",
#             "StartedAt": "2021-01-26T06:55:44.928510218Z",
#             "FinishedAt": "2021-01-25T14:19:31.316854657Z"
#         },
#         ...
#         "Mounts": [
#             {
#                 "Type": "volume",
#                 "Name": "notes-db-data",
#                 "Source": "/var/lib/docker/volumes/notes-db-data/_data",
#                 "Destination": "/var/lib/postgresql/data",
#                 "Driver": "local",
#                 "Mode": "z",
#                 "RW": true,
#                 "Propagation": ""
#             }
#         ],
#         ...
#         "NetworkSettings": {
#             ...
#             "Networks": {
#                 "bridge": {
#                     "IPAMConfig": null,
#                     "Links": null,
#                     "Aliases": null,
#                     "NetworkID": "e4c7ce50a5a2a49672155ff498597db336ecc2e3bbb6ee8baeebcf9fcfa0e1ab",
#                     "EndpointID": "2a2587f8285fa020878dd38bdc630cdfca0d769f76fc143d1b554237ce907371",
#                     "Gateway": "172.17.0.1",
#                     "IPAddress": "172.17.0.2",
#                     "IPPrefixLen": 16,
#                     "IPv6Gateway": "",
#                     "GlobalIPv6Address": "",
#                     "GlobalIPv6PrefixLen": 0,
#                     "MacAddress": "02:42:ac:11:00:02",
#                     "DriverOpts": null
#                 },
#                 "notes-api-network": {
#                     "IPAMConfig": {},
#                     "Links": null,
#                     "Aliases": [
#                         "37755e86d627"
#                     ],
#                     "NetworkID": "06579ad9f93d59fc3866ac628ed258dfac2ed7bc1a9cd6fe6e67220b15d203ea",
#                     "EndpointID": "5b8f8718ec9a5ec53e7a13cce3cb540fdf3556fb34242362a8da4cc08d37223c",
#                     "Gateway": "172.18.0.1",
#                     "IPAddress": "172.18.0.2",
#                     "IPPrefixLen": 16,
#                     "IPv6Gateway": "",
#                     "GlobalIPv6Address": "",
#                     "GlobalIPv6PrefixLen": 0,
#                     "MacAddress": "02:42:ac:12:00:02",
#                     "DriverOpts": {}
#                 }
#             }
#         }
#     }
# ]
</code></pre><p>Ho accorciato l'output per facilitare la visualizzazione. Sul mio sistema, il container <code>notes-db</code> è in esecuzione, utilizza il volume <code>notes-db-data</code> ed è collegato alla rete bridge <code>notes-api-network</code>.</p><p>Una volta che ti sei assicurato che è tutto in regola, puoi eseguire un nuovo container con il seguente comando:</p><pre><code>docker container run \
    --detach \
    --name=notes-api \
    --env DB_HOST=notes-db \
    --env DB_DATABASE=notesdb \
    --env DB_PASSWORD=secret \
    --publish=3000:3000 \
    --network=notes-api-network \
    notes-api
    
# f9ece420872de99a060b954e3c236cbb1e23d468feffa7fed1e06985d99fb919</code></pre><p>Dovresti essere in grado di capire da solo questo lungo comando, quindi esaminerò brevemente le variabili di ambiente.</p><p>L'applicazione <code>notes-api</code> richiede di impostare tre variabili di ambiente:</p><ul><li><code>DB_HOST</code> - È l'host del server del database. Dato che sia il database del server che l'API sono collegati alla stessa rete bridge definita da un utente, il server del database può essere indicato usando il nome del suo container, che in questo caso è <code>notes-db</code>.</li><li><code>DB_DATABASE</code> - Il database che userà questa API. <a href="#come-eseguire-il-server-del-database">Eseguendo il server del database</a>, abbiamo impostato il nome predefinito su <code>notesdb</code> usando la variabile di ambiente <code>POSTGRES_DB</code>. La useremo qui.</li><li><code>DB_PASSWORD</code> - La password per connettere il database. Anche questa è stata impostata nella sezione <a href="#come-eseguire-il-server-del-database">Eseguire il server del database</a>, usando la variabile di ambiente <code>POSTGRES_PASSWORD</code>.</li></ul><p>Per vedere se il container è correttamente in esecuzione o no, puoi usare il comando <code>container ls</code>:</p><pre><code>docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                    NAMES
# f9ece420872d   notes-api     "docker-entrypoint.s…"   12 minutes ago   Up 12 minutes   0.0.0.0:3000-&gt;3000/tcp   notes-api
# 37755e86d627   postgres:12   "docker-entrypoint.s…"   17 hours ago     Up 14 minutes   5432/tcp                 notes-db</code></pre><p>Il container adesso è in esecuzione. Puoi andare su <code>http://127.0.0.1:3000/</code> per vedere l'API in azione.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/bonjour-mon-ami.png" class="kg-image" alt="bonjour-mon-ami" width="600" height="400" loading="lazy"></figure><p>L'API ha 5 rotte in totale, che puoi vedere nel file <code>/notes-api/api/api/routes/notes.js</code>.</p><p>Sebbene il container sia in esecuzione, c'è un'ultima cosa che devi fare prima di poterlo usare. Dovrai eseguire la migrazione del database necessaria per configurare le tabelle del database, e puoi farlo eseguendo il comando <code>npm run db:migrate</code> all'interno del container.</p><h3 id="come-eseguire-i-comandi-in-un-container-in-esecuzione"><strong>Come eseguire i comandi in un container in esecuzione</strong></h3><p>Hai già imparato come eseguire i comandi in un container stoppato. Un'altra situazione è eseguire un comando in un container in esecuzione.</p><p>Per questo dovrai usare il comando <code>exec</code> per eseguire un comando personalizzato all'interno del container in esecuzione.</p><p>La sintassi generica per il comando <code>exec</code> è la seguente:</p><pre><code>docker container exec &lt;identificatore container&gt; &lt;comando&gt;</code></pre><p>Per eseguire <code>npm run db:migrate</code> all'interno del container <code>notes-api</code>, puoi usare il seguente comando:</p><pre><code>docker container exec notes-api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
#
# Using environment: production
# Batch 1 run: 1 migrations</code></pre><p>In casi in cui vuoi eseguire un comando interattivo all'interno di un container in esecuzione, dovrai usare l'opzione <code>-it</code>. Come esempio, se vuoi accedere alla shell in esecuzione all'interno del container <code>notes-api</code>, puoi usare il comando:</p><pre><code>docker container exec -it notes-api sh

# / # uname -a
# Linux b5b1367d6b31 5.10.9-201.fc33.x86_64 #1 SMP Wed Jan 20 16:56:23 UTC 2021 x86_64 Linux</code></pre><h3 id="come-scrivere-degli-script-di-gestione-in-docker"><strong>Come scrivere degli script di gestione in Docker</strong></h3><p>Gestire un progetto multi-container insieme a rete, volumi e compagnia, significa scrivere un sacco di comandi. Per semplificare il processo, di solito mi servo di semplici <a href="https://opensource.com/article/17/1/getting-started-shell-scripting">script di shell</a> e di un <a href="https://opensource.com/article/18/8/what-how-makefile" rel="noopener noreferrer">Makefile</a>.</p><p>Troverai i seguenti quattro script di shell nella cartella <code>notes-api</code>:</p><ul><li><code>boot.sh</code> - Usato per avviare i container, se esistono già.</li><li><code>build.sh</code> - Crea ed esegue i container. All'occorrenza crea anche immagini, volumi e reti.</li><li><code>destroy.sh</code> - Rimuove tutti i container, volumi e reti associati a un progetto.</li><li><code>stop.sh</code> - Ferma tutti i container in esecuzione.</li></ul><p>C'è anche un <code>Makefile</code> che contiene quattro target chiamati <code>start</code>, <code>stop</code>, <code>build</code> e <code>destroy</code>, ognuno dei quali invoca i precedenti script di shell.</p><p>Eseguire <code>make stop</code> dovrebbe fermare tutti i container in esecuzione sul tuo sistema. Eseguire <code>make destroy</code> dovrebbe fermare tutti i container e rimuovere tutto. Assicurati di lanciare gli script all'interno della cartella <code>notes-api</code>:</p><pre><code>make destroy

# ./shutdown.sh
# stopping api container ---&gt;
# notes-api
# api container stopped ---&gt;

# stopping db container ---&gt;
# notes-db
# db container stopped ---&gt;

# shutdown script finished

# ./destroy.sh
# removing api container ---&gt;
# notes-api
# api container removed ---&gt;

# removing db container ---&gt;
# notes-db
# db container removed ---&gt;

# removing db data volume ---&gt;
# notes-db-data
# db data volume removed ---&gt;

# removing network ---&gt;
# notes-api-network
# network removed ---&gt;

# destroy script finished</code></pre><p>Se stai ottenendo un errore di autorizzazione negata (permission denied), allora esegui <code>chmod +x</code> sugli script:</p><pre><code>chmod +x boot.sh build.sh destroy.sh shutdown.sh</code></pre><p>Non spiegherò questi script perché sono semplici istruzioni <code>if-else</code> insieme ad alcuni comandi Docker che hai già visto molte volte. Se hai un po' di conoscenza della shell di Linux, dovresti essere in grado di capire anche questi script.</p><h2 id="come-comporre-i-progetti-usando-docker-compose"><strong>Come comporre i progetti usando Docker-Compose</strong></h2><p>Nella sezione precedente, hai imparato come gestire un progetto multi-container e le difficoltà che implicate. Invece di scrivere così tanti comandi, esiste un modo più facile per gestire dei progetti multi-container, grazie a uno strumento chiamato <a href="https://docs.docker.com/compose/" rel="noopener noreferrer">Docker Compose</a>.</p><p>Secondo la <a href="https://docs.docker.com/compose/">documentazione di Docker</a>:</p><blockquote>Compose è uno strumento per definire ed eseguire applicazioni multi-container in Docker. Con compose si utilizza un file YAML per configurare i servizi di un'applicazione. Poi, con un singolo comando, si creano e avviano tutti i servizi dalla configurazione.</blockquote><p>Sebbene Compose funzioni in tutti gli ambienti, è più incentrato sullo sviluppo e il collaudo. Usare Compose in un ambiente di produzione non è assolutamente consigliato.</p><h3 id="le-basi-di-docker-compose"><strong>Le basi di Docker Compose</strong></h3><p>Vai nella cartella in cui hai clonato il repository in dotazione con questo manuale. Vai nella cartella <code>notes-api/api</code> e crea un file <code>Dockerfile.dev</code>. Inserisci al suo interno il seguente codice:</p><pre><code class="language-dockerfile"># stage one
FROM node:lts-alpine as builder

# install dependencies for node-gyp
RUN apk add --no-cache python make g++

WORKDIR /app

COPY ./package.json .
RUN npm install

# stage two
FROM node:lts-alpine

ENV NODE_ENV=development

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY . .
COPY --from=builder /app/node_modules /home/node/app/node_modules

CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]</code></pre><p>Il codice è quasi identico al <code>Dockerfile</code> con cui hai lavorato nella sezione precedente, eccetto per le seguenti tre differenze:</p><ul><li>Nella riga 10, eseguiamo <code>npm install</code> invece di <code>npm run install --only=prod</code> perché vogliamo anche le dipendenze di sviluppo.</li><li>Nella riga 15, impostiamo la variabile di ambiente <code>NODE_ENV</code> su <code>development</code> invece di <code>production</code>.</li><li>Nella riga 24, usiamo uno strumento chiamato <a href="https://nodemon.io/" rel="noopener noreferrer">nodemon</a> per ottenere la funzionalità di ricaricamento rapido per l'API.</li></ul><p>Sai già che questo progetto ha due container:</p><ul><li><code>notes-db</code> - Un server del database alimentato da PostgreSQL.</li><li><code>notes-api</code> - Una API REST alimentata da Express.js</li></ul><p>Nel mondo di Compose, ogni container che costituisce l'applicazione è detto servizio. Il primo passo nel comporre un progetto multi-container è definire questi servizi.</p><p>Proprio come il demone Docker utilizza un <code>Dockerfile</code> per il build delle immagini, Docker Compose usa un file <code>docker-compose.yaml</code> da cui legge le definizioni dei servizi.</p><p>Vai nella cartella <code>notes-api</code> e crea un nuovo file <code>docker-compose.yaml</code>. Metti il seguente codice all'interno del file appena creato:</p><pre><code class="language-yaml">version: "3.8"

services: 
    db:
        image: postgres:12
        container_name: notes-db-dev
        volumes: 
            - notes-db-dev-data:/var/lib/postgresql/data
        environment:
            POSTGRES_DB: notesdb
            POSTGRES_PASSWORD: secret
    api:
        build:
            context: ./api
            dockerfile: Dockerfile.dev
        image: notes-api:dev
        container_name: notes-api-dev
        environment: 
            DB_HOST: db ## same as the database service name
            DB_DATABASE: notesdb
            DB_PASSWORD: secret
        volumes: 
            - /home/node/app/node_modules
            - ./api:/home/node/app
        ports: 
            - 3000:3000

volumes:
    notes-db-dev-data:
        name: notes-db-dev-data</code></pre><p>Un file <code>docker-compose.yaml</code> valido inizia con la definizione della versione del file. Al momento della stesura di questo manuale, l'ultima versione era <code>3.8</code>. Puoi controllare <a href="https://docs.docker.com/compose/compose-file/">qui</a> l'ultima versione.</p><p>I blocchi in un file YAML sono definiti dall'indentazione. Esaminerò ogni blocco e spiegherò cosa fa.</p><ul><li>Il blocco <code>services</code> contiene le definizioni per ognuno dei servizi o container nell'applicazione. <code>db</code> e <code>api</code> sono i due servizi che costituiscono questo progetto.</li><li>Il blocco <code>db</code> definisce un nuovo servizio nell'applicazione e contiene le informazioni necessarie per avviare il container. Ogni servizio richiede un'immagine predefinita o un <code>Dockerfile</code> per l'esecuzione di un container. Per il servizio <code>db</code> stiamo usando l'immagine ufficiale di PostgreSQL.</li><li>A differenza del servizio <code>db</code>, non esiste un'immagine di cui è già stato fatto il build per il servizio <code>api</code>. Quindi useremo il file <code>Dockerfile.dev</code>.</li><li>Il blocco <code>volumes</code> definisce ogni volume con nome necessario per ogni servizio. Al momento elenca solo il volume <code>notes-db-dev-data</code> usato dal servizio <code>db</code>.</li></ul><p>Ora che hai avuto una panoramica ad alto livello del file <code>docker-compose.yaml</code>, diamo un'occhiata più da vicino ai singoli servizi.</p><p>Il codice della definizione per il servizio <code>db</code> è il seguente:</p><pre><code class="language-yaml">db:
    image: postgres:12
    container_name: notes-db-dev
    volumes: 
        - db-data:/var/lib/postgresql/data
    environment:
        POSTGRES_DB: notesdb
        POSTGRES_PASSWORD: secret</code></pre><ul><li>La chiave <code>image</code> contiene il repository e il tag dell'immagine usata per questo contenitore. Stiamo eseguendo l'immagine <code>postgres:12</code> per eseguire il container del database.</li><li><code>container_name</code> indica il nome del container. Di default i container sono chiamati seguendo la sintassi <code>&lt;nome directory progetto&gt;_&lt;nome servizio&gt;</code>. Puoi sovrascriverlo usando <code>container_name</code>.</li><li>L'array <code>volumes</code> contiene la mappatura dei volumi per il servizio e supporta volumi con nome, volumi anonimi e bind mount. La sintassi <code>&lt;sorgente&gt;:&lt;destinazione&gt;</code> è identica a quella che hai già visto prima.</li><li>La mappa <code>environment</code> contiene i valori delle diverse variabili di ambiente necessarie per il servizio.</li></ul><p>Il codice di definizione per il servizio <code>api</code> è il seguente:</p><pre><code class="language-yaml">api:
    build:
        context: ./api
        dockerfile: Dockerfile.dev
    image: notes-api:dev
    container_name: notes-api-dev
    environment: 
        DB_HOST: db ## same as the database service name
        DB_DATABASE: notesdb
        DB_PASSWORD: secret
    volumes: 
        - /home/node/app/node_modules
        - ./api:/home/node/app
    ports: 
        - 3000:3000</code></pre><ul><li>Il servizio <code>api</code> non ha un'immagine di cui è già stato fatto il build, ma possiede una configurazione di build. Sotto il blocco <code>build</code> definiamo il contesto e il nome del Dockerfile per il build di un'immagine. Ormai dovresti avere una buona comprensione del contesto e del Dockerfile, quindi non mi ci soffermerò oltre.</li><li>La chiave <code>image</code> contiene il nome dell'immagine di cui fare il build. Se non assegnato, all'immagine verrà dato un nome seguendo la sintassi <code>&lt;nome directory progetto&gt;_&lt;nome servizio&gt;</code>.</li><li>All'interno della mappa <code>environment</code>, la variabile <code>DB_HOST</code> dà prova di una funzionalità di Compose, ovvero che puoi fare riferimento a un altro servizio nella stessa applicazione usando il suo nome. Quindi, <code>db</code> sarà sostituito dall'indirizzo IP del container del servizio <code>api</code>. Le variabili <code>DB_DATABASE</code> e <code>DB_PASSWORD</code> devono corrispondere rispettivamente con <code>POSTGRES_DB</code> e <code>POSTGRES_PASSWORD</code> dalla definizione del servizio <code>db</code>.</li><li>Nella mappa <code>volumes</code>, puoi vedere descritti un volume anonimo e un bind mount. La sintassi è identica a quella che hai visto nelle sezionii precedenti.</li><li>La mappa <code>ports</code> definisce ogni port mapping. La sintassi, <code>&lt;porta host&gt;:&lt;porta container&gt;</code> è identica all'opzione <code>--publish</code> usata precedentemente.</li></ul><p>Infine, il codice per <code>volumes</code> è il seguente:</p><pre><code class="language-yaml">volumes:
    db-data:
        name: notes-db-dev-data</code></pre><p>Ogni volume con un nome usato in qualsiasi dei servizi deve essere definito qui. Se non definisci un nome, il volume sarà chiamato seguendo <code>&lt;nome directory progetto&gt;_&lt;chiave volume&gt;</code> dove, in questo caso, la chiave è <code>db-data</code>.</p><p>Puoi imparare le diverse opzioni per la configurazione dei volumi nella <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes">documentazione ufficiale</a>.</p><h3 id="come-avviare-i-servizi-in-docker-compose"><strong>Come avviare i servizi in Docker Compose</strong></h3><p>Esistono diversi modi per avviare dei servizi definiti in un file YAML. Il primo comando che imparerai a conoscere è <code>up</code>. Il comando <code>up</code> svolge il build di ogni immagine mancante, crea container e li avvia in un colpo solo.</p><p>Prima di eseguire il comando, assicurati di aver aperto il terminale nella stessa directory in cui si trova il file <code>docker-compose.yaml</code>. Questo è molto importante per tutti i comandi <code>docker-compose</code> che esegui.</p><pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network "notes-api_default" with the default driver
# Creating volume "notes-db-dev-data" with default driver
# Building api
# Sending build context to Docker daemon  37.38kB
#
# Step 1/13 : FROM node:lts-alpine as builder
#  ---&gt; 471e8b4eb0b2
# Step 2/13 : RUN apk add --no-cache python make g++
#  ---&gt; Running in 197056ec1964
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 197056ec1964
#  ---&gt; 6609935fe50b
# Step 3/13 : WORKDIR /app
#  ---&gt; Running in 17010f65c5e7
# Removing intermediate container 17010f65c5e7
#  ---&gt; b10d12e676ad
# Step 4/13 : COPY ./package.json .
#  ---&gt; 600d31d9362e
# Step 5/13 : RUN npm install
#  ---&gt; Running in a14afc8c0743
### LONG INSTALLATION STUFF GOES HERE ###
#  Removing intermediate container a14afc8c0743
#  ---&gt; 952d5d86e361
# Step 6/13 : FROM node:lts-alpine
#  ---&gt; 471e8b4eb0b2
# Step 7/13 : ENV NODE_ENV=development
#  ---&gt; Running in 0d5376a9e78a
# Removing intermediate container 0d5376a9e78a
#  ---&gt; 910c081ce5f5
# Step 8/13 : USER node
#  ---&gt; Running in cfaefceb1eff
# Removing intermediate container cfaefceb1eff
#  ---&gt; 1480176a1058
# Step 9/13 : RUN mkdir -p /home/node/app
#  ---&gt; Running in 3ae30e6fb8b8
# Removing intermediate container 3ae30e6fb8b8
#  ---&gt; c391cee4b92c
# Step 10/13 : WORKDIR /home/node/app
#  ---&gt; Running in 6aa27f6b50c1
# Removing intermediate container 6aa27f6b50c1
#  ---&gt; 761a7435dbca
# Step 11/13 : COPY . .
#  ---&gt; b5d5c5bdf3a6
# Step 12/13 : COPY --from=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; 9e1a19960420
# Step 13/13 : CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]
#  ---&gt; Running in 5bdd62236994
# Removing intermediate container 5bdd62236994
#  ---&gt; 548e178f1386
# Successfully built 548e178f1386
# Successfully tagged notes-api:dev
# Creating notes-api-dev ... done
# Creating notes-db-dev  ... done</code></pre><p>L'opzione <code>--detach</code> o <code>-d</code> funziona nello stesso modo che hai visto precedentemente. L'opzione <code>--file</code> o <code>-f</code> è necessaria solo se il file YAML non è chiamato <code>docker-compose.yaml</code> (ma l'ho usata qui a scopo di dimostrazione).</p><p>Oltre al comando <code>up</code>, c'è il comando <code>start</code>. La differenza principale tra i due è che il comando <code>start</code> non crea container mancanti, ma avvia solo container già esistenti. In pratica è l'equivalente del comando <code>container start</code>.</p><p>L'opzione <code>--build</code> per il comando <code>up</code> forza un rebuild delle immagini. Ci sono alcune altre opzioni per il comando <code>up</code> che puoi vedere nella <a href="https://docs.docker.com/compose/reference/up/">documentazione ufficiale</a>.</p><h3 id="come-elencare-i-servizi-in-docker-compose"><strong>Come elencare i servizi in Docker Compose</strong></h3><p>Sebbene i container dei servizi avviati da Compose possano essere elencati usando il comando <code>container ls</code>, esiste il comando <code>ps</code> per elencare i container definiti soltanto in YAML.</p><pre><code>docker-compose ps

#     Name                   Command               State           Ports         
# -------------------------------------------------------------------------------
# notes-api-dev   docker-entrypoint.sh ./nod ...   Up      0.0.0.0:3000-&gt;3000/tcp
# notes-db-dev    docker-entrypoint.sh postgres    Up      5432/tcp</code></pre><p>Non dà tutte le informazioni che fornisce l'output di <code>container ls</code>, ma è utile quando ci sono un sacco di container in esecuzione simultanea.</p><h3 id="come-eseguire-dei-comandi-all-interno-di-un-servizio-in-esecuzione-in-docker-compose"><strong>Come eseguire dei comandi all'interno di un servizio in esecuzione in Docker Compose</strong></h3><p>Spero che ricordi dalla sezione precedente che per creare le tabelle del database per questa API devi eseguire alcuni script di migrazione.</p><p>Proprio come per il comando <code>container exec</code>, c'è un comando <code>exec</code> per <code>docker-compose</code>. La sintassi generica del comando è la seguente:</p><pre><code>docker-compose exec &lt;nome servizio&gt; &lt;comando&gt;</code></pre><p>Per eseguire il comando <code>npm run db:migrate</code> all'interno del servizio <code>api</code>, puoi eseguire il seguente comando:</p><pre><code>docker-compose exec api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
# 
# Using environment: development
# Batch 1 run: 1 migrations</code></pre><p>A differenza del comando <code>container exec</code>, non devi passare il flag <code>-it</code> per le sessioni interattive. <code>docker-compose</code> lo fa automaticamente.</p><h3 id="come-accedere-ai-log-da-un-servizio-in-esecuzione-in-docker-compose"><strong>Come accedere ai log da un servizio in esecuzione in Docker Compose</strong></h3><p>Puoi anche usare il comando <code>logs</code> per recuperare i log da un servizio in esecuzione. La sintassi generica del comando è:</p><pre><code>docker-compose logs &lt;nome servizio&gt;</code></pre><p>Per accedere ai log dal servizio <code>api</code>, esegui il seguente comando:</p><pre><code>docker-compose logs api

# Attaching to notes-api-dev
# notes-api-dev | [nodemon] 2.0.7
# notes-api-dev | [nodemon] reading config ./nodemon.json
# notes-api-dev | [nodemon] to restart at any time, enter `rs`
# notes-api-dev | [nodemon] or send SIGHUP to 1 to restart
# notes-api-dev | [nodemon] ignoring: *.test.js
# notes-api-dev | [nodemon] watching path(s): *.*
# notes-api-dev | [nodemon] watching extensions: js,mjs,json
# notes-api-dev | [nodemon] starting `node bin/www`
# notes-api-dev | [nodemon] forking
# notes-api-dev | [nodemon] child pid: 19
# notes-api-dev | [nodemon] watching 18 files
# notes-api-dev | app running -&gt; http://127.0.0.1:3000</code></pre><p>Questa è solo una porzione dell'output. Puoi agganciarti al flusso di output del servizio e ottenere i log in tempo reale usando l'opzione <code>-f</code> o <code>--follow</code>. Ogni log successivo verrà mostrato istantaneamente nel terminale finché non esci premendo <code>ctrl + c</code> o chiudendo la finestra. L'esecuzione del container continuerà anche se esci dalla finestra dei log.</p><h3 id="come-fermare-dei-servizi-in-docker-compose"><strong>Come fermare dei servizi in Docker Compose</strong></h3><p>Per stoppare dei servizi, puoi intraprendere due approcci. Il primo è il comando <code>down</code>, che blocca tutti i container in esecuzione e li rimuove dal sistema. Rimuove anche tutte le reti:</p><pre><code>docker-compose down --volumes

# Stopping notes-api-dev ... done
# Stopping notes-db-dev  ... done
# Removing notes-api-dev ... done
# Removing notes-db-dev  ... done
# Removing network notes-api_default
# Removing volume notes-db-dev-data</code></pre><p>L'opzione <code>--volumes</code> indica che vuoi rimuovere ogni volume con nome definito nel blocco <code>volumes</code>. Puoi imparare quali sono le opzioni aggiuntive per il comando <code>down</code> nella <a href="https://docs.docker.com/compose/reference/down/">documentazione ufficiale</a>.</p><p>Un altro modo per stoppare i servizi è il comando <code>stop</code>, che funziona allo stesso modo del comando <code>container stop</code>. Ferma tutti i container di un'applicazione e li mantiene. Questi container possono essere riavviati in seguito con il comando <code>start</code> o <code>up</code>.</p><h3 id="come-comporre-un-applicazione-full-stack-in-docker-compose"><strong>Come comporre un'applicazione full-stack in Docker Compose</strong></h3><p>In questa sezione, aggiungeremo un front-end alla nostra API e la trasformeremo in un'applicazione full-stack completa. Non spiegherò nulla riguardo ai file <code>Dockerfile.dev</code> (eccetto quello per il servizio <code>nginx</code>) dato che sono identici a quelli esaminati nelle sezioni precedenti.‌</p><p>Se hai clonato il repository con il codice del progetto, vai nella cartella <code>fullstack-notes-application</code>. Ogni cartella all'interno della radice del progetto contiene il codice per ogni servizio e il corrispondente <code>Dockerfile</code>.‌</p><p>Prima di partire con il file <code>docker-compose.yaml</code>, diamo un'occhiata al diagramma che illustra come funzionerà l'applicazione:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/fullstack-application-design.svg" class="kg-image" alt="fullstack-application-design" width="600" height="400" loading="lazy"></figure><p>Invece di accettare richieste direttamente, come prima, in questa applicazione tutte le richieste saranno ricevute da un servizio NGINX (che chiameremo router).</p><p>Il router poi valuterà se l'end-point richiesto ha <code>/api</code> al suo interno. Se è così, instraderà la richiesta al back-end o, in caso contrario, al front-end.</p><p>Questo perché un'applicazione front-end non viene eseguita in un container, ma nel browser, servito da un container. Come risultato, il networking di Compose non funziona come atteso e l'applicazione front-end fallisce nel trovare il servizio <code>api</code>.</p><p>NGINX, d'altro canto, viene eseguito in un container e può comunicare con i diversi servizi nell'intera applicazione.</p><p>Non mi addentrerò nella configurazione di NGINX. Si tratta di un argomento fuori dallo scopo di questo manuale, ma se vuoi dare un'occhiata, guarda pure i file <code>/notes-api/nginx/development.conf</code> e <code>/notes-api/nginx/production.conf</code>. Il codice per <code>/notes-api/nginx/Dockerfile.dev</code> è il seguente:</p><pre><code>FROM nginx:stable-alpine

COPY ./development.conf /etc/nginx/conf.d/default.conf</code></pre><p>Tutto ciò che fa è copiare la configurazione del file in <code>/etc/nginx/conf.d/default.conf</code> all'interno del container.</p><p>Iniziamo scrivendo il file <code>docker-compose.yaml</code>. Oltre ai servizi <code>api</code> e <code>db</code>, ci saranno i servizi <code>client</code> e <code>nginx</code>. Ci saranno anche delle definizioni di rete di cui parlerò a breve.</p><pre><code class="language-yaml">version: "3.8"

services: 
    db:
        image: postgres:12
        container_name: notes-db-dev
        volumes: 
            - db-data:/var/lib/postgresql/data
        environment:
            POSTGRES_DB: notesdb
            POSTGRES_PASSWORD: secret
        networks:
            - backend
    api:
        build: 
            context: ./api
            dockerfile: Dockerfile.dev
        image: notes-api:dev
        container_name: notes-api-dev
        volumes: 
            - /home/node/app/node_modules
            - ./api:/home/node/app
        environment: 
            DB_HOST: db ## same as the database service name
            DB_PORT: 5432
            DB_USER: postgres
            DB_DATABASE: notesdb
            DB_PASSWORD: secret
        networks:
            - backend
    client:
        build:
            context: ./client
            dockerfile: Dockerfile.dev
        image: notes-client:dev
        container_name: notes-client-dev
        volumes: 
            - /home/node/app/node_modules
            - ./client:/home/node/app
        networks:
            - frontend
    nginx:
        build:
            context: ./nginx
            dockerfile: Dockerfile.dev
        image: notes-router:dev
        container_name: notes-router-dev
        restart: unless-stopped
        ports: 
            - 8080:80
        networks:
            - backend
            - frontend

volumes:
    db-data:
        name: notes-db-dev-data

networks: 
    frontend:
        name: fullstack-notes-application-network-frontend
        driver: bridge
    backend:
        name: fullstack-notes-application-network-backend
        driver: bridge
</code></pre><p>Il file è quasi identico al precedente su cui hai lavorato. L'unica cosa che richiede delle spiegazioni è la configurazione della rete. Il codice per il blocco <code>networks</code> è il seguente:</p><pre><code class="language-yaml">networks: 
    frontend:
        name: fullstack-notes-application-network-frontend
        driver: bridge
    backend:
        name: fullstack-notes-application-network-backend
        driver: bridge</code></pre><p>Ho definito due reti bridge. Di default, Compose crea una rete bridge a cui collega tutti i container. In questo progetto, tuttavia, volevo una rete appropriatamente isolata. Quindi ho definito due reti, una per i servizi front-end e una per i servizi back-end.</p><p>Ho anche aggiunto un blocco <code>networks</code> in ogni definizione dei servizi. In questo modo i servizi <code>api</code> e <code>db</code> saranno collegati a una rete e il servizio <code>client</code> sarà collegato a una rete separata. Ma il servizio <code>nginx</code> sarà collegato a entrambe le reti in modo da agire come router tra i servizi front-end e back-end.</p><p>Avvia tutti i servizi eseguendo il seguente comando:</p><pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network "fullstack-notes-application-network-backend" with driver "bridge"
# Creating network "fullstack-notes-application-network-frontend" with driver "bridge"
# Creating volume "notes-db-dev-data" with default driver
# Building api
# Sending build context to Docker daemon  37.38kB
# 
# Step 1/13 : FROM node:lts-alpine as builder
#  ---&gt; 471e8b4eb0b2
# Step 2/13 : RUN apk add --no-cache python make g++
#  ---&gt; Running in 8a4485388fd3
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 8a4485388fd3
#  ---&gt; 47fb1ab07cc0
# Step 3/13 : WORKDIR /app
#  ---&gt; Running in bc76cc41f1da
# Removing intermediate container bc76cc41f1da
#  ---&gt; 8c03fdb920f9
# Step 4/13 : COPY ./package.json .
#  ---&gt; a1d5715db999
# Step 5/13 : RUN npm install
#  ---&gt; Running in fabd33cc0986
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container fabd33cc0986
#  ---&gt; e09913debbd1
# Step 6/13 : FROM node:lts-alpine
#  ---&gt; 471e8b4eb0b2
# Step 7/13 : ENV NODE_ENV=development
#  ---&gt; Using cache
#  ---&gt; b7c12361b3e5
# Step 8/13 : USER node
#  ---&gt; Using cache
#  ---&gt; f5ac66ca07a4
# Step 9/13 : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; 60094b9a6183
# Step 10/13 : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; 316a252e6e3e
# Step 11/13 : COPY . .
#  ---&gt; Using cache
#  ---&gt; 3a083622b753
# Step 12/13 : COPY --from=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; Using cache
#  ---&gt; 707979b3371c
# Step 13/13 : CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]
#  ---&gt; Using cache
#  ---&gt; f2da08a5f59b
# Successfully built f2da08a5f59b
# Successfully tagged notes-api:dev
# Building client
# Sending build context to Docker daemon  43.01kB
# 
# Step 1/7 : FROM node:lts-alpine
#  ---&gt; 471e8b4eb0b2
# Step 2/7 : USER node
#  ---&gt; Using cache
#  ---&gt; 4be5fb31f862
# Step 3/7 : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; 1fefc7412723
# Step 4/7 : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; d1470d878aa7
# Step 5/7 : COPY ./package.json .
#  ---&gt; Using cache
#  ---&gt; bbcc49475077
# Step 6/7 : RUN npm install
#  ---&gt; Using cache
#  ---&gt; 860a4a2af447
# Step 7/7 : CMD [ "npm", "run", "serve" ]
#  ---&gt; Using cache
#  ---&gt; 11db51d5bee7
# Successfully built 11db51d5bee7
# Successfully tagged notes-client:dev
# Building nginx
# Sending build context to Docker daemon   5.12kB
# 
# Step 1/2 : FROM nginx:stable-alpine
#  ---&gt; f2343e2e2507
# Step 2/2 : COPY ./development.conf /etc/nginx/conf.d/default.conf
#  ---&gt; Using cache
#  ---&gt; 02a55d005a98
# Successfully built 02a55d005a98
# Successfully tagged notes-router:dev
# Creating notes-client-dev ... done
# Creating notes-api-dev    ... done
# Creating notes-router-dev ... done
# Creating notes-db-dev     ... done</code></pre><p>Adesso vai su <code>http://localhost:8080</code> e voilà!</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/01/notes-application.png" class="kg-image" alt="notes-application" width="600" height="400" loading="lazy"></figure><p>Prova ad aggiungere e cancellare delle note per vedere se l'applicazione funziona adeguatamente. Il progetto comprende anche degli script da shell e un <code>Makefile</code>. Sperimenta per vedere come puoi eseguire questo progetto senza l'aiuto di <code>docker-compose</code> come hai fatto nella sezione precedente.</p><h2 id="conclusione"><strong>Conclusione</strong></h2><p>Ti ringrazio dal profondo del mio cuore per il tempo che hai speso leggendo questo manuale. Spero che ti sia piaciuto e che tu abbia imparato tutto l'essenziale di Docker.</p><p>Oltre questo, ho scritto manuali completi su altri argomenti complicati che sono disponibili gratuitamente su <a href="https://www.freecodecamp.org/news/author/farhanhasin/">freeCodeCamp</a>.</p><p>Questi manuali sono parte della mia missione di semplificare per tutti l'apprendimento di tecnologie difficili da comprendere. La stesura di ognuno di questi manuali richiede molto tempo e impegno.</p><p>Se ti è piaciuto leggere questo manuale e vuoi motivarmi, considera di lasciarmi delle stelle su <a href="https://github.com/fhsinchy/">GitHub</a> e sostienimi per le mie competenze su <a href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Accetto anche il <a href="https://www.buymeacoffee.com/farhanhasin">supporto tramite buymeacoffee</a>.</p><p>Sono sempre aperto a suggerimenti e discussioni su <a href="https://twitter.com/frhnhsin">Twitter</a> o <a href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Contattami con un messaggio privato.</p><p>Infine, considera di condividere queste risorse con altri:</p><blockquote>Condividere il sapere è l'atto di amicizia più importante, perché è un modo in cui puoi dare qualcosa senza perdere nulla. — Richard Stallman</blockquote><p>Buono studio, ci vediamo alla prossima.</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
