{{indexmenu_n>20}}
## Comment on fabrique une image Docker ?
On va essayer de fabriquer notre première image Docker. Soyons fous, on va faire une image d'un serveur web assez connu, `nginx`, pour mettre en ligne notre premier site sur les serveurs de Picasoft. ^_^
On va supposer pour cette page que tu es [[technique:tech_team:ssh|connecté sur la machine de test]].
Tu arrives dans ton dossier personnel, et tu vas pouvoir créer un fichier `Dockerfile`, qui est la recette de cuisine que va suivre Docker pour **construire** l'image. En d'autres termes, Docker va partir d'une image pré-existante, exécuter toutes tes instructions, et enregistrer le tout dans une nouvelle image.
## Un serveur web vierge
Pour créer et éditer un fichier, tu peux utiliser l'éditeur `nano`, dans le terminal. Une fois que tu as fini d'éditer ton fichier, appuie sur `Ctrl+X`, `Y` et `Enter` pour quitter. Tu peux ensuite utiliser la commande `cat` pour lire le contenu du fichier.
{{ :technique:tech_team:nano.gif |}}
### Écrire la recette de cuisine
Un Dockerfile commence toujours par une instruction `FROM`. On indique de quelle image on veut partir. En général, c'est une distribution Linux. On utilise beaucoup Debian à Picasoft, une distribution robuste et stable. La première ligne sera donc :
```
FROM debian
```
On peut préciser une version de l'image en la suffixant avec `:`. Exemples : `debian:strech`, `debian:buster`... C'est même une Bonne Pratique™, mais on va pas partir dans les détails. :-P
Ensuite, on va utiliser l'instruction `RUN` pour indiquer les commandes qu'on veut lancer. C'est ici qu'il faut se demander :
Quelles commandes j'aurai lancé pour installer `nginx` sur ma machine Linux ?
On regarde sur la [documentation d'installation](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#installing-prebuilt-debian-packages), et on voit qu'il y a deux commandes à lancer. Alors c'est parti, on rajoute des `RUN` dans le Dockerfile pour indiquer qu'il faut lancer ces commandes :
```
RUN apt-get update
RUN apt-get install -y nginx
```
Petite subtilité, on a rajouté un `-y` (« yes » pour ne pas demander confirmation), puisque la commande sera exécutée sans interaction par Docker.
Ensuite, il faut savoir qu'nginx écoute sur le port 80, le port standard pour le web.
C'est quoi un port ?
Il faut imaginer un port comme une **porte de chambre dans un hôtel**. Puisque plusieurs applications peuvent tourner sur un même ordinateur, si on veut les contacter de l'extérieur, il faut bien un moyen pour indiquer que c'est à telle application que je veux parler et pas à une autre. C'est l'idée des ports : chaque application choisit un port sur lequel elle **écoute**, et dès que quelqu'un tape à son port, elle sait que ça lui est destiné.
{{ :technique:tech_team:port.png |}}
Conséquence : un port ne peut être écouté que par une seule application, sinon on se retrouve avec le problème de ne pas savoir à qui est destiné le message.
On va donc indiquer à l'image qu'il faudra ouvrir le port 80, le rendre contactable par l'extérieur ! Pour ce faire on utilise l'instruction :
```
EXPOSE 80
```
On a presque fini. Il manque une instruction essentielle : celle qui va indiquer à l'image ce qu'il faut faire quand on démarrera un conteneur.
Il faut faire quoi ?
Il faut lancer le processus nginx, pour que le serveur web tourne ! Et en l'occurrence, il faut le lancer en **premier plan**.
Heu, comment ça en premier plan ?
Sur Linux et ailleurs, les programmes peuvent se lancer en premier plan ou en arrière plan, dans un terminal. En premier plan, ça veut dire que tant que le logiciel n'a pas fini de s'exécuter, on ne peut plus rien faire dans le terminal. En arrière plan (on appelle ça un **démon**, ou //daemon//), on a la main sur le terminal et on peut continuer à taper des commandes.
Sauf que si nginx est lancé en arrière plan, ce qui est le défaut, Docker va chercher une autre commande à exécuter, et comme il n'y en a pas, il va juste... s'arrêter. Voilà pourquoi il faut toujours que la commande lancée par le conteneur à son démarrage soit en premier plan.
Bref, pour faire ça, il faut passer à nginx l'argument `-g 'daemon off;'`. La commande à lancer s'indiquer avec l'instruction `CMD`, suivi d'un tableau `["commande", "argument 1", "argument 2", "..."]`. Ce qui nous donne :
```
CMD ["nginx", "-g", "daemon off;"]
```
On arrive donc à notre premier Dockerfile :
```
FROM debian
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
### Lancer la cuisson
Alors c'est bien beau tout ça, mis comment on construit l'image ?
```
docker build -t nginx:quentin .
```
- `-t` (pour //tag//) permet de donner un nom (`nginx`) et une étiquette (`quentin`) à l'image que l'on va construire. Si on est plusieurs à construire l'image, chacun prendra une étiquette différente.
- `.` indique le dossier où se trouve le Dockerfile. Dans le monde Linux, `.` indique le dossier courant.
{{ :technique:tech_team:first_build.gif |}}
On voit toutes les étapes défiler, la plus longue étant le téléchargement et l'installation de `nginx`.
Docker maintient un **cache** de chacune des étapes de la construction de l'image, c'est-à-dire que si tu relances la construction d'une image, Docker n'aura pas besoin de tout refaire, il a gardé en mémoire les résultats intermédiaires de chaque étape, aussi appelée *layer*. Si tu relances une deuxième fois...
{{ :technique:tech_team:second_build.gif |}}
### À table !
Maintenant qu'on a notre image qui contient tout ce dont on a besoin, on va l'instancier, c'est-à-dire créer un conteneur à partir de l'image. C'est la commande `docker run` qui fait ça, et je te propose de lui passer quelques arguments :
```
docker run -d --name quentin nginx:quentin
```
- `-d` (//detach//) pour lancer le conteneur en arrière plan, sinon on reste bloqué tant qu'il n'est pas éteint
- `--name` pour préciser le nom du conteneur, ici `quentin`. Sinon il prend un nom au hasard et c'est pas facile de le manipuler...
- et enfin, l'image à lancer, celle qu'on vient de construire !
On peut lancer autant de conteneurs qu'on veut à partir de cette image, du moment qu'ils ont un nom différent.
La commande `docker ps` permet de voir que le conteneur a bien été lancé.
La commande `curl` permet de consulter une page web depuis un terminal. Il suffit de lui donner une URL, et par défaut, `curl` utilise le port 80.
On devrait donc pouvoir accéder à la page d'accueil par défaut de `nginx` !
{{ :technique:tech_team:curl_failed.gif |}}
Comment ça, connexion refusée ? m(
En fait, on a oublié un détail. C'est que le conteneur a ses propres ports. On a bien indiqué au conteneur d'ouvrir son port 80, mais quand on tape `pica01-test.picasoft.net`, on parle à la machine, pas au conteneur... La situation est la suivante :
{{ :technique:tech_team:port_unlink.png |}}
Il faudrait donc indiquer à Docker que quand on toque au port 80 de la machine hôte depuis Internet (ce que fait `curl`), il faut en réalité aller toquer au port 80 du conteneur `quentin` !
Pour ce faire, on utilise l'argument `-p :`.
À chaque fois que tu crées un nouveau conteneur, il faudra supprimer l'ancien avant puisque deux conteneurs ne peuvent pas avoir le même nom. Pour ce faire :
```
docker rm -f quentin
```
où tu remplaces `quentin` par le nom de ton conteneur.
Aussi, je n'aurais pas dû utiliser le port 80 de l'hôte car il est souvent utilisé par un autre service, appelé Traefik. Tu peux l'éteindre en faisant :
```
docker stop traefik
```
Ça nous donne :
```
docker run -d --name quentin -p 80:80 nginx:quentin
```
On se retrouve donc dans la situation suivante :
{{ :technique:tech_team:port_ok.png |}}
Alors, ça donne quoi ?
{{ :technique:tech_team:curl_ok.gif |}}
On voit bien le code de la page d'accueil de `nginx` ! Nickel ! :-D
Ouais enfin bon, un site web c'est quand même bien avec un peu de contenu, non ?
## Pimper le serveur web
Un serveur web sans contenu, c'est comme une fondue sans fromage : un peu sec.
Alors, comment on change la page d'accueil de nginx, par exemple ?
nginx sert les fichiers qui se trouvent dans le dossier `/var/www/html`. Si on ne lui précise rien, il sert le fichier `index.html`. Notre but, c'est donc de remplacer le fichier `/var/www/html/index.html` dans le conteneur nginx ! :-D
Tu vas créer un dossier `website` dans lequel se trouvera un fichier `index.html`, avec le contenu que tu veux.
Exemple :
{{ :technique:tech_team:index_nginx.gif |}}
### Modifier la recette
Une des premières idées, c'est de modifier l'image pour qu'elle embarque avec elle le fichier `index.html`.
Dans un Dockerfile, la syntaxe pour copier un fichier est la suivante :
```
COPY