Ora che abbiamo assimilato le nozioni di base per poterlo fare, costruiamo la nostra prima immagine!

La CLI di Docker mette a disposizione un comando preposto alla costruzione delle immagini, ovvero:

docker image build

Al solito cerchiamo di capire cosa mette a disposizione utilizzando l’help. Digitiamo docker image build –help.

Usage: docker image build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

Options:
      --add-host list           Add a custom host-to-IP mapping (host:ip)
      --build-arg list          Set build-time variables
      --cache-from strings      Images to consider as cache sources
      --cgroup-parent string    Optional parent cgroup for the container
      --compress                Compress the build context using gzip
      --cpu-period int          Limit the CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int           Limit the CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int          CPU shares (relative weight)
      --cpuset-cpus string      CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string      MEMs in which to allow execution (0-3, 0,1)
      --disable-content-trust   Skip image verification (default true)
  -f, --file string             Name of the Dockerfile (Default is 'PATH/Dockerfile')
      --force-rm                Always remove intermediate containers
      --iidfile string          Write the image ID to the file
      --isolation string        Container isolation technology
      --label list              Set metadata for an image
  -m, --memory bytes            Memory limit
      --memory-swap bytes       Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --network string          Set the networking mode for the RUN instructions during build (default "default")
      --no-cache                Do not use cache when building the image
      --platform string         Set platform if server is multi-platform capable
      --pull                    Always attempt to pull a newer version of the image
  -q, --quiet                   Suppress the build output and print image ID on success
      --rm                      Remove intermediate containers after a successful build (default true)
      --security-opt strings    Security options
      --shm-size bytes          Size of /dev/shm
      --squash                  Squash newly built layers into a single new layer
      --stream                  Stream attaches to server to negotiate build context
  -t, --tag list                Name and optionally a tag in the 'name:tag' format
      --target string           Set the target build stage to build.
      --ulimit ulimit           Ulimit options (default [])

Questa volta le opzioni sono davvero molte, ma vorrei portare l’attenzione in particolare su due di esse.

La prima prevede l’applicazione del flag –no-cache, che permette di costruire l’immagine senza utilizzare i layer già presenti nella cache dell’host.

L’altra utilizza il flag –tag o -t nella sua forma compatta.
Anche se il suo nome fa pensare al fatto che indichi il tag dell’immagine, in realtà è così solo in parte. Il flag -t infatti permette di dare un nome ad un’immagine e solo opzionalmente un tag.

Prima immagine

Rimbocchiamoci le mani e partiamo!

Come prima cosa creiamo una nuova cartella, io l’ho chiamata “prima-immagine” ma il nome non è importante. Al suo interno andiamo a creare un Dockerfile. Ricordo che il Dockerfile è un file che si chiama “Dockerfile” senza estensione.

All’interno del Dockerfile andiamo a scrivere il seguente contenuto:

ARG VERSION=3.8
#immagine di partenza
FROM alpine:$VERSION

LABEL maintainer="fabio@moku.io"

# dichiarazione variabile per poterla usare anche durante la build,
# altrimenti non sarebbe stato possibile utilizzare la variabile dopo
# l'esecuzione del comando FROM
ARG VERSION

# modalità bash
RUN echo $VERSION

# modalità exec
RUN ["echo", "$VERSION" ]

# comando eseguito all'avvio di un container a partire da questa immagine
CMD [ "/bin/sh"]

Il Dockerfile in questione è semplicissimo, scarica la versione 3.8 di alpine, fa qualche stampa e poi inizia l’esecuzione con una shell.

Per cominciare, posizioniamoci sulla cartella appena creata ed eseguiamo il comando:

docker image build .

L’output prodotto – riportato di seguito – ci permette di osservare cosa accade nel processo di build.

Sending build context to Docker daemon  2.048kB
Step 1/7 : ARG VERSION=3.8
Step 2/7 : FROM alpine:$VERSION
3.8: Pulling from library/alpine
486039affc0a: Pull complete 
Digest: sha256:2bb501e6173d9d006e56de5bce2720eb06396803300fe1687b58a7ff32bf4c14
Status: Downloaded newer image for alpine:3.8
 ---> c8bccc0af957
Step 3/7 : LABEL maintainer="fabio@moku.io"
 ---> Running in 6b5e40e8dd39
Removing intermediate container 6b5e40e8dd39
 ---> 744f5b13876d
Step 4/7 : ARG VERSION
 ---> Running in d009efefb200
Removing intermediate container d009efefb200
 ---> 14750b682604
Step 5/7 : RUN echo $VERSION
 ---> Running in 859a8d754e0d
3.8
Removing intermediate container 859a8d754e0d
 ---> c0281f996ed9
Step 6/7 : RUN ["echo", "$VERSION" ]
 ---> Running in ea6a64f4b715
$VERSION
Removing intermediate container ea6a64f4b715
 ---> 699309c84290
Step 7/7 : CMD [ "/bin/sh"]
 ---> Running in b981f5f8a0d4
Removing intermediate container b981f5f8a0d4
 ---> 7f17e8021e95
Successfully built 7f17e8021e95

Pongo l’attenzione in particolare al passo 5, dove viene stampato il contenuto della variabile VERSION, mentre al passo 6 l’interpretazione della variabile non è stata eseguita. Questo è esattamente il comportamento che ci aspettavamo dalle due diverse notazioni.

Al termine del processo di build ci viene indicato che l’immagine è stata costruita con successo riportando il suo id (7f17e8021e95). Eseguendo il list delle immagini infatti troveremo quella appena costruita con lo stesso valore nella colonna IMAGE ID e sarà in testa all’elenco perché è la più recente.

REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
<none>                                  <none>              7f17e8021e95        4 minutes ago       4.41MB

Notiamo che repository e tag di tale immagine sono senza valore. Per impostare questi valori per un’immagine è necessario specificarli durante la build con il flag –tag o -t.
Eseguiamo quindi:

docker image build -t prima-immagine:0.1 .

Ottenendo questo output:

Sending build context to Docker daemon  2.048kB
Step 1/7 : ARG VERSION=3.8
Step 2/7 : FROM alpine:$VERSION
 ---> c8bccc0af957
Step 3/7 : LABEL maintainer="fabio@moku.io"
 ---> Using cache
 ---> 744f5b13876d
Step 4/7 : ARG VERSION
 ---> Using cache
 ---> 14750b682604
Step 5/7 : RUN echo $VERSION
 ---> Using cache
 ---> c0281f996ed9
Step 6/7 : RUN ["echo", "$VERSION" ]
 ---> Using cache
 ---> 699309c84290
Step 7/7 : CMD [ "/bin/sh"]
 ---> Using cache
 ---> 7f17e8021e95
Successfully built 7f17e8021e95
Successfully tagged prima-immagine:0.1

La build è praticamente istantanea perché tutti i layer sono già in cache e questa volta vediamo che l’ultima istruzione dice che l’immagine è stata “taggata” con successo.

L’id dell’immagine è rimasto lo stesso e, siccome prima l’immagine non aveva ne nome ne tag, facendo il listing delle immagini troveremo una sola occorrenza dell’immagine con il nome ed il tag appena descritti. Se non ci credi, prova a eseguire docker image ls.

Se tale combinazione di nome e tag fosse stata già presente, sarebbe stata inserita un’altra riga nella tabella corrispondente allo stesso image id.  Per verificare basta eseguire l’istruzione di build con un tag diverso, ad esempio latest (docker image build -t prima-immagine .) e verificare nuovamente con docker image ls.

Come vedete da quest’ultimo comando, se nella build viene specificato un nome ma non un tag, verrà associato di default  il tag “latest”.

Un’altra cosa che salta all’occhio è che il timestamp di creazione di un’immagine viene preservata a prescindere da quando le vengono associati i tag.


Prosegui su: Caricamento sul Docker Hub

Immagine:  Albero foto creata da diana.grytsku – it.freepik.com