Come Installare e Usare Docker su Ubuntu

Introduzione Docker è un’applicazione che semplifica il processo di gestione dei processi delle applicazioni attraverso i container. I container consentono di eseguire le applicazioni in dei processi con risorse isolate. […]

Avatar di GB Factory
GB Factory 8 Luglio 2022

Introduzione

Docker è un'applicazione che semplifica il processo di gestione dei processi delle applicazioni attraverso i container. I container consentono di eseguire le applicazioni in dei processi con risorse isolate. I container sono simili a delle macchine virtuali, tuttavia hanno il vantaggio di essere più facili da trasportare, utilizzano meno risorse e sono più dipendenti dal sistema operativo del server in cui vengono eseguiti.

Come Installare e Usare Docker su Ubuntu

In questo tutorial vedremo come installare e utilizzare la Community Edition (CE) di Docker su Ubuntu 20.04. Installeremo Docker, lavoreremo con i container e le con le immagini e infine aggiungeremo un'immagine a una Repository di Docker.

Prerequisiti

Per seguire con facilità questo tutorial avrai bisogno di:

Step 1 - Installare Docker

Il pacchetto per installare Docker è disponibile nella repository ufficiale di Ubuntu, tuttavia non è sempre aggiornato all'ultima versione. Per essere sicuri di installare sempre l'ultima versione, andremo ad installare Docker direttamente dalla repository ufficiale.

Per farlo andiamo ad aggiungere una nuova sorgente per il pacchetto, aggiungeremo poi la Chiave GPG da Docker per essere sicuri che i download siano validi e infine installeremo il pacchetto.

Come prima cosa aggiorniamo l'elenco dei nostri pacchetti:

sudo apt update

Poi andiamo a installare alcuni pacchetti necessari per consentire ad apt di lavorare con pacchetti tramite HTTPS:

sudo apt install apt-transport-https ca-certificates curl software-properties-common

Aggiungiamo la Chiave GPG per la repository ufficiale di Docker al nostro sistema:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

E aggiungiamo anche la repository di Docker all'elenco delle sorgenti di apt:

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"

Questo comando servirà anche ad aggiornare il nostro database di pacchetti, aggiungendo i pacchetti di Docker dalla repository che abbiamo appena specificato.

Prima di procedere con l'installazione, dobbiamo essere sicuri che l'installazione venga eseguita dalla repository di Docker e non da quella di Ubuntu, eseguiamo quindi il comando:

apt-cache policy docker-ce

L'output dovrebbe essere simile al seguente, anche se ovviamente la versione di Docker potrebbe cambiare:

docker-ce:
  Installed: (none)
  Candidate: 5:19.03.9~3-0~ubuntu-focal
  Version table:
     5:19.03.9~3-0~ubuntu-focal 500
        500 https://download.docker.com/linux/ubuntu focal/stable amd64 Packages

Possiamo vedere come docker-ce non sia installato, ma nel caso volessimo installarlo verrebbe scaricato dalla repository di Docker per Ubuntu (focal).

Ora siamo finalmente pronti ad installare Docker:

sudo apt install docker-ce

Ora Docker dovrebbe essere stato installato, il demone avviato e il processo impostato in modo da essere avviato al boot del sistema.

Controlliamo che sia in esecuzione con:

sudo systemctl status docker

Nell'ouput dovremmo poter vedere che il servizio è attivo e in esecuzione:

● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2020-05-19 17:00:41 UTC; 17s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 24321 (dockerd)
      Tasks: 8
     Memory: 46.4M
     CGroup: /system.slice/docker.service
             └─24321 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Instando Docker non avremo solamente accesso al servizio di Docker (demone) ma anche all'utility di Docker da riga di comando (docker) e al client Docker. Più avanti nel tutorial vedremo come utilizzare docker da riga di comando.

Step 2 - Eseguire i Comandi Docker senza Sudo (Opzionale)

Di default i comandi docker possono essere utilizzare solamente dall'utente root o dagli utenti appartenendo al gruppo docker, creato durante il processo di installazione di Docker. Infatti, se proverai ad eseguire un domando docker senza aggiungere sudo prima di esso, o senza essere nel gruppo docker, vedrai questo messaggio:

docker: Cannot connect to the Docker daemon. Is the docker daemon running on this host?.
See 'docker run --help'.

Se vuoi evitare di dover aggiungere sudo ogni volta che vuoi eseguire un comando docker, puoi aggiungere il tuo utente al gruppo docker appena citato:

sudo usermod -aG docker ${USER}

Per rendere effettivi i cambiamenti, è necessario uscire dal server e rientrare, oppure eseguendo il seguente comando:

su - ${USER}

Ti verrà chiesto di eseguire la password del tuo utente e poi potrai continuare. Una volta effettuato l'accesso puoi confermare che il tuo utente sia nel gruppo docker digitando il seguente comando:

groups
gbfactory sudo docker

Se devi aggiungere al gruppo docker un utente a cui non hai effettuato l'accesso, puoi eseguire il comando dichiarando esplicitamente il nome dell'utente:

sudo usermod -aG docker nome_utente

Durante il resto dell'articolo daremo per scontato che i comandi docker vengano eseguiti da un utente all'interno del gruppo docker. Se non hai eseguito questo step dovrai aggiungere sudo prima del comando.

Step 3 - Utilizzare il Comando Docker

L'utilizzo del comando docker consiste nel passare una serie di opzioni e comandi seguiti dai relativi parametri. La sintassi di base è la seguente:

docker [opzione] [comando] [parametri]

Per vedere un elenco di tutti i comandi disponibili puoi digitare:

docker

Alla versione di Docker 20.10.14, la lista completa di tutti i sottocomandi disponibili include:

  attach      Attach local standard input, output, and error streams to a running container
  build       Build an image from a Dockerfile
  commit      Create a new image from a container's changes
  cp          Copy files/folders between a container and the local filesystem
  create      Create a new container
  diff        Inspect changes to files or directories on a container's filesystem
  events      Get real time events from the server
  exec        Run a command in a running container
  export      Export a container's filesystem as a tar archive
  history     Show the history of an image
  images      List images
  import      Import the contents from a tarball to create a filesystem image
  info        Display system-wide information
  inspect     Return low-level information on Docker objects
  kill        Kill one or more running containers
  load        Load an image from a tar archive or STDIN
  login       Log in to a Docker registry
  logout      Log out from a Docker registry
  logs        Fetch the logs of a container
  pause       Pause all processes within one or more containers
  port        List port mappings or a specific mapping for the container
  ps          List containers
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rename      Rename a container
  restart     Restart one or more containers
  rm          Remove one or more containers
  rmi         Remove one or more images
  run         Run a command in a new container
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  search      Search the Docker Hub for images
  start       Start one or more stopped containers
  stats       Display a live stream of container(s) resource usage statistics
  stop        Stop one or more running containers
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
  top         Display the running processes of a container
  unpause     Unpause all processes within one or more containers
  update      Update configuration of one or more containers
  version     Show the Docker version information
  wait        Block until one or more containers stop, then print their exit codes

Per vedere le opzioni disponibili per un determinato comando puoi usare:

docker sottocomando_docker --help

Puoi anche visualizzare le informazioni generali relative a Docker puoi utilizzare:

docker info

Step 4 - Lavorare con le Immagini Docker

I contenitori Docker (containers) sono creati da immagini Docker (images). Di default, Docker recupera le immagini dal Docker Hub, un registro gestito direttamente da Docker, ovvero l'azienda dietro il progetto Docker. Tutti possono hostare le proprie immagini Docker sul Docker Hub, di conseguenza la maggior parte dei programmi e delle distribuzioni Linux hanno una loro immagine sull'Hub.

Per controllare se puoi accedere e scaricare le immagini dal Docker Hub, puoi scrivere:

docker run hello-world

All'interno dell'output vedrai se Docker funziona correttamente:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

...

Docker inizialmente non era in grado di trovare l'immagine hello-world in locale, quindi l'ha scaricata dal Docker Hub, che è la repository predefinita. Una volta che l'immagine è stata scaricata, Docker creerà un contenitore dall'immagine ed eseguirà l'applicazione all'interno del contenitore, mostrando il messaggio.

Puoi anche cercare le immagini disponibili nel Docker Hub utilizzando il comando docker e il sottocomando search. Per esempio, possiamo cercare l'immagine di Ubuntu con:

docker search ubuntu

Questo script recupererà dal Docker Hub una lista di tutte le immagini il cui nome contiene quello che è stato specificato nel comando. In questo caso l'output sarà simile al seguente:

NAME                             DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
ubuntu                           Ubuntu is a Debian-based Linux operating sys…   14048     [OK]
websphere-liberty                WebSphere Liberty multi-architecture images …   283       [OK]
ubuntu-upstart                   DEPRECATED, as is Upstart (find other proces…   112       [OK]
...

Nella colonna OFFICIAL, la dicitura OK indica che l'immagine è supportata dall'azienda dietro il progetto. Una volta aver identificato l'immagine di tuo interesse, puoi scaricarla in locale utilizzando il sottocomando pull.

Per scaricare in locale l'immagine ufficiale di ubuntu puoi eseguire il seguente comando:

docker pull ubuntu

Apparirà il seguente output:

Using default tag: latest
latest: Pulling from library/ubuntu
e0b25ef51634: Pull complete
Digest: sha256:9101220a875cee98b016668342c489ff0674f247f6ca20dfc91b91c0f28581ae
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

Dopo che un'immagine sarà stata scaricata, puoi eseguire un contenitore utilizzando l'immagine scaricata con il sottocomando run. Come abbiamo visto con l'esempio hello-world, se un'immagine non è stata scaricata quando docker viene eseguito con il comando run, il client Docker scaricherà l'immagine automaticamente, poi eseguirà un container con quell'immagine.

Per vedere l'elenco delle immagini che sono state scaricate in locale, scrivi:

docker images

L'output sarà simile al seguente:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              1d622ef86b13        3 weeks ago         73.9MB
hello-world         latest              bf756fb1ae65        4 months ago        13.3kB

Come vedremo più avanti nel tutorial, le immagini che utilizzi per eseguire i container possono essere modificare e utilizzate per generare nuovi immagini, che potranno anche poi essere caricate sul Docker Hub o in altri registri Docker.

Step 5 - Eseguire un Docker Container

Il contenitore hello-world che abbiamo eseguito nello step precedente è un esempio di un contenitore che viene eseguito e si ferma dopo aver stampato un messaggio testuale. I contenitori possono essere ovviamente molto più utili di così, e possono anche essere interattivi. Sono simili alle macchine virtuali, però molto più efficienti dal punto di vista delle risorse utilizzate.

Come esempio, andiamo ad eseguire un container utilizzando l'ultima immagine di Ubuntu. La combinazione dei flag -i e -t consentono di avere una shell interattiva e potrai accedere al container:

docker run -it ubuntu

Il prompt dei comandi dovrebbe cambiare per riflettere il fatto che stai ora lavorando all'interno di un container e dovrebbe diventare simile al seguente:

[email protected]:/#

Come puoi vedere è stato aggiunto l'ID del container al prompt, che in questo caso è d9b100f2f636. Questo ID è fondamentale per identificare il container quando vogliamo cancellarlo.

Ora possiamo eseguire qualsiasi comando all'interno del container. Per esempio, possiamo aggiornare il database dei pacchetti interno al container. Non dobbiamo aggiungere il prefisso sudo dato che stiamo già lavorando con l'utente root:

[email protected]:/# apt update

Poi possiamo anche installare delle applicazioni, per esempio Node.js:

[email protected]:/# apt install nodejs

Questo comando installerà la versione di Node.js contenuta nella repository ufficiale di Ubuntu. Quando l'installazione sarà completa, possiamo verificare che Node.js sia stato installato:

[email protected]:/# node -v

Vedremo in console il numero della versione installata:

v12.22.9

Qualsiasi cambiamento che faremo all'interno di questo container avrà effetti solamente sul container in questione.

Per uscire dal container possiamo scrivere il seguente comando nel prompt:

exit

Step 6 - Gestire i Container Docker

Con un utilizzo costante di Docker potremmo arrivare a lavorare con molti container in esecuzione su una stessa macchina. Per visualizzare tutti i contenitori attivi possiamo utilizzare:

docker ps

L'output sarà simile al seguente:

CONTAINER ID        IMAGE               COMMAND             CREATED

In questa guida abbiamo avviato due container, il primo dall'immagine hello-world, il secondo dall'immagine ubuntu. Entrambe i container attualmente non sono più in esecuzione, ma esistono comunque sul sistema.

Per vedere quindi tutti i container, sia attivi che inattivi, possiamo eseguire lo stesso comando di prima aggiungendo il flag -a:

docker ps -a

Vedremo quindi un output come il seguente:

CONTAINER ID   IMAGE         COMMAND   CREATED         STATUS                     PORTS     NAMES
1c08a7a0d0e4   ubuntu        "bash"     About a minute ago   Exited (0) 7 seconds ago             dazzling_taussig
587000e49d53   hello-world   "/hello"   5 minutes ago        Exited (0) 5 minutes ago             adoring_kowalevski

Possiamo vedere l'ultimo container che abbiamo creato passando il flag -l:

docker ps -l
CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS                     PORTS     NAMES
1c08a7a0d0e4   ubuntu    "bash"    3 minutes ago   Exited (0) 2 minutes ago             dazzling_taussig

Per avviare un container bloccato, utilizza docker start, seguito dall'ID del container oppure dal nome del container. Possiamo avviare il container di Ubuntu utilizzando l'ID 1c08a7a0d0e4:

docker start 1c08a7a0d0e4

Verrà avviato il container, e possiamo utilizzare docker ps per visualizzare il suo stato:

CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS         PORTS     NAMES
1c08a7a0d0e4   ubuntu    "bash"    6 minutes ago   Up 8 seconds             dazzling_taussig

Per fermare un container in esecuzione possiamo utilizzare docker stop, seguito dall'ID o dal nome del container. Questa volta proviamo a fermare il container utilizzando il nome assegnato ad esso da Docker, in questo caso dazzling_taussig:

docker stop dazzling_taussig

Se dovessimo decidere che un container non è più necessario, possiamo eliminarlo utilizzando il comando docker rm, seguito ancora una volta dall'ID o dal nome del container. Possiamo utilizzare il comando docker ps -a per trovare l'ID o il nome del container associato all'immagine hello-world e rimuoverlo.

docker rm adoring_kowalevski

Possiamo avviare un nuovo container con un nome specifico utilizzando il parametro --name. Possiamo anche utilizzare il parametro --rm per creare un container che si rimuove automaticamente quando viene fermato. Per maggiori informazioni si può consultare l'output del comando docker run help.

Step 7 - Creare un'Immagine Docker a partire da un Container modificato

Abbiamo visto che avviando un'immagine Docker possiamo creare, modificare e cancellare file proprio come è possibile fare in una macchina virtuale. Questi cambiamenti saranno applicati solo al container in questione. Possiamo anche avviare e fermare il container, ma una volta che lo elimineremo con il comando docker rm, tutti i cambiamenti saranno persi per sempre.

In questa sezione vedremo come salvare un container, nello stato in cui si trova, in una nuova immagine Docker.

Dopo aver installato Node.js all'interno del nostro container Ubuntu, abbiamo ancora un container in esecuzione a partire da un'immagine, tuttavia il container è diverso dall'immagine utilizzata per crearlo. Potremmo però voler riutilizzare questo container con Ubuntu e Node.js in futuro, anche come base per nuove immagini.

Possiamo eseguire il commit dei cambiamenti effettuati ad una nuova istanza dell'immagine Docker utilizzando il seguente comando:

docker commit -m "Il cambiamento che è stato fatto" -a "Nome Autore" id_container repository/nome_nuova_immagine

Il flag -m serve per specificare il messaggio del commit, che aiuta gli altri a sapere che cambiamenti sono stati fatti, mentre -a specifica l'autore dei cambiamenti. Il parametro id_container è l'ID del contenitore che ci siamo annotati prima, mentre lavoravamo sul container dalla sessione Docker interattiva. A meno che tu non abbia più repository diverse, repository è il tuo nome utente sul Docker Hub.

Per esempio, per l'utente gbfactory e il container con ID d9b100f2f636, il comando sarà:

docker commit -m "Installato Node.js" -a "gbfactory" d9b100f2f636 gbfactory/ubuntu-nodejs

Quando eseguiamo il commit su un'immagine, una nuova immagine sarà salvata in locale sulla tua macchina. Più avanti nella guida vedremo come inviare (push) un'immagine sul registro del Docker Hub, in modo che anche altre persone possano effettuare l'accesso.

Elencando le immagini Docker vedremo anche l'immagine che abbiamo appena salvato, così come quella vecchia da cui deriva l'immagine nuova:

docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
gbfactory/ubuntu-nodejs   latest              7c1f35226ca6        7 seconds ago       179MB
...

In questo esempio, ubuntu-nodejs è la nuova immagine, che deriva dall'immagine ubuntu scaricata dal Docker Hub. Come possiamo notare dalla differenza nella dimensione delle due immagini, intuiamo che i cambiamenti sono stati salvati nella nuova immagine. Quindi, la prossima volta che si sarà necessario creare un container con Ubuntu e Node.js già installato, potrete usare questa nuova immagine.

Possiamo anche costruire le immagini da un Dockerfile, che consente di automatizzare l'installazione dei programmi in una nuova immagine. Tuttavia, questo va oltre lo scopo di questa guida.

Step 8 - Pubblicare un'Immagine sul Docker Hub

Il passo successivo dopo aver creato una nuova immagine partendo da un'immagine esistente, è quello di condividerla con il pubblico sul Docker Hub oppure su un'altra repository Docker. Per eseguire questa operazione, chiamata push, è necessario avere un account sul Docker Hub.

Come prima cosa, iniziamo la procedura di accesso al Docker Hub:

docker login -u username-account-docker

Successivamente ci verrà chiesto di inserire la password del nostro account sul Docker Hub. Se i dati sono corretti, l'accesso sarà eseguito con successo.

Nota: se il nome utente scelto sul registro Docker è diverso dal nome utente locale utilizzato per creare l'immagine, dovrai taggare la tua immagine con il nome utente del registro. Per esempio, utilizzando l'immagine creata durante lo step precedente, il comando sarebbe:

docker tag gbfactory/ubuntu-nodejs nomeutente-dockerhub/ubuntu-nodejs

Ora siamo pronti per fare il push della nostra immagine:

docker push username-dockerhub/nome-immagine-docker

Se per esempio dobbiamo caricare l'immagine ubuntu-nodejs all'interno della repository gbfactory, il comando sarebbe:

docker push gbfactory/ubuntu-nodejs

Questo processo potrebbe richiedere un po' di tempo per il completamento dato che deve caricare l'immagine, ma quando sarà completato, l'output sarà simile al seguente:

The push refers to a repository [docker.io/gbfactory/ubuntu-nodejs]
e3fbbfb44187: Pushed
5f70bf18a086: Pushed
a3b5c80a4eba: Pushed
7f18b442972b: Pushed
3ce512daaf78: Pushed
7aae4540b42d: Pushed

...

Dopo aver completato il caricamento dell'immagine sul registro, dovrebbe essere visibile sulla dashboard del tuo account, dovrebbe apparire come nell'immagine seguente:

Se il vostro tentativo di push è fallito con un'errore simile a questo, il motivo è probabilmente da ricercarsi sul login non effettuato o non andato a buon fine:

The push refers to a repository [docker.io/gbfactory/ubuntu-nodejs]
e3fbbfb44187: Preparing
5f70bf18a086: Preparing
a3b5c80a4eba: Preparing
7f18b442972b: Preparing
3ce512daaf78: Preparing
7aae4540b42d: Waiting
unauthorized: authentication required

Proviamo ad eseguire nuovamente l'accesso con docker login e ripetiamo il push. Poi verifichiamo se la nostra immagine esiste sulla repository del Docker Hub.

Possiamo ora utilizzare il comando docker pull gbfactory/ubuntu-nodejs per scaricare l'immagine su una nuova macchina e utilizzarla per avviare un nuovo container.

Conclusione

In questo tutorial abbiamo visto come installare Docker, lavorare con le immagini e con i container e anche come eseguire il push di un'immagine modificata al Docker Hub.