Apprenez les bases de la création d'images Docker personnalisées en écrivant votre premier Dockerfile. Maîtrisez les instructions clés et le processus de build.
Introduction : pourquoi créer ses propres images Docker ?
Si Docker Hub offre une myriade d'images prêtes à l'emploi, il arrive un moment où vous devez aller plus loin. Que ce soit pour déployer votre propre application, personnaliser un environnement de développement spécifique, ou simplement pour intégrer une configuration particulière qui n'existe pas sur le Hub, la création de vos propres images Docker devient indispensable. C'est là qu'intervient le Dockerfile.
Un Dockerfile est un simple fichier texte qui contient une série d'instructions. Ces instructions décrivent, étape par étape, comment assembler une image Docker. Considérez-le comme la recette de cuisine pour votre environnement applicatif : il définit l'image de base à utiliser, les logiciels à installer, les fichiers de configuration à copier, les ports à exposer, et la commande à exécuter lorsque le conteneur démarre. L'avantage majeur est la reproductibilité : n'importe qui possédant ce Dockerfile peut reconstruire exactement la même image, garantissant ainsi la cohérence entre les environnements (développement, test, production).
Ce chapitre vous guidera dans la création de votre toute première image personnalisée. Nous allons explorer la structure de base d'un Dockerfile, passer en revue les instructions les plus essentielles pour commencer, apprendre à utiliser la commande `docker build` pour transformer ce fichier en une image concrète, et enfin, lancer un conteneur à partir de votre création. Préparez-vous à devenir un constructeur d'images Docker !
Anatomie d'un Dockerfile simple : la recette de base
Un Dockerfile est lu de haut en bas par le démon Docker lors du processus de construction (`build`). Chaque instruction crée une nouvelle couche dans l'image finale. Commençons par imaginer un scénario simple : nous voulons une image qui exécute un serveur Nginx servant une page HTML personnalisée.
Voici à quoi pourrait ressembler un Dockerfile minimaliste pour cet objectif :
# Utiliser une image officielle Nginx comme base
FROM nginx:stable-alpine
# Définir le répertoire de travail par défaut dans l'image
WORKDIR /usr/share/nginx/html
# Copier notre fichier HTML local dans le répertoire de travail de l'image
COPY index.html .
# Informer Docker que le conteneur écoutera sur le port 80 à l'exécution (informatif)
EXPOSE 80
# La commande par défaut exécutée au lancement du conteneur (héritée de l'image de base ici)
# CMD ["nginx", "-g", "daemon off;"] - Pas besoin de redéfinir si l'image de base convient
Ce fichier illustre déjà plusieurs concepts clés :
`FROM` : Toute image commence par une image de base. Ici, nous utilisons une version légère (`alpine`) et stable de Nginx.
`WORKDIR` : Définit le chemin où les commandes suivantes (`COPY`, `RUN`, `CMD`, `ENTRYPOINT`) seront exécutées. C'est une bonne pratique pour organiser votre image.
`COPY` : Permet de copier des fichiers ou des répertoires depuis votre machine locale (le contexte de build) vers le système de fichiers de l'image. Ici, on copie `index.html` (qui doit exister à côté du Dockerfile) dans `/usr/share/nginx/html` (le `.` fait référence au `WORKDIR`).
`EXPOSE` : Documente le port sur lequel l'application à l'intérieur du conteneur écoutera. C'est purement informatif pour l'utilisateur et pour certains outils, cela n'ouvre pas réellement le port sur l'hôte (c'est le rôle de l'option `-p` de `docker run`).
`CMD` : Définit la commande par défaut qui sera exécutée lorsque le conteneur démarrera à partir de cette image. Dans cet exemple, nous n'avons pas besoin de la redéfinir car l'image `nginx` de base a déjà une `CMD` appropriée pour lancer Nginx.
Nous allons maintenant détailler ces instructions et quelques autres commandes essentielles.
Instructions essentielles décortiquées : les ingrédients clés
Pour construire des images efficaces, il est crucial de comprendre le rôle des instructions fondamentales du Dockerfile :
`FROM image[:tag]` : Toujours la première instruction (sauf exceptions avec `ARG`). Elle spécifie l'image de base sur laquelle vous allez construire. Le choix de l'image de base est important pour la taille et la sécurité. Les images `alpine` sont souvent privilégiées pour leur légèreté. Exemple : `FROM python:3.9-slim`.
`WORKDIR /chemin/du/repertoire` : Définit le répertoire de travail pour les instructions `RUN`, `CMD`, `ENTRYPOINT`, `COPY` et `ADD` qui suivent. Si le répertoire n'existe pas, il sera créé. Utiliser `WORKDIR` est préférable à des commandes `RUN cd ...`. Exemple : `WORKDIR /app`.
`COPY [--chown=:] ... ` : Copie des fichiers ou répertoires depuis le contexte de build (votre machine locale) vers le système de fichiers de l'image. `` est relatif au contexte, `` est un chemin absolu dans l'image ou relatif au `WORKDIR`. L'option `--chown` permet de définir le propriétaire des fichiers copiés. Utilisez `COPY` de préférence à `ADD` pour la copie de fichiers locaux simples. Exemple : `COPY ./mon_app /app`.
`RUN ` (shell form) ou `RUN ["executable", "param1", "param2"]` (exec form) : Exécute une commande dans une nouvelle couche au-dessus de l'image actuelle. Utilisé typiquement pour installer des paquets (`apt-get update && apt-get install -y ...`, `pip install -r requirements.txt`), créer des répertoires, compiler du code, etc. Chaque `RUN` crée une couche, il est donc conseillé de chaîner les commandes avec `&&` pour limiter le nombre de couches. Exemple : `RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*`.
`EXPOSE [/]...` : Informe Docker que le conteneur écoutera sur les ports réseau spécifiés lors de son exécution. Le protocole par défaut est TCP. C'est une documentation ; pour publier le port sur l'hôte, utilisez l'option `-p` de `docker run`. Exemple : `EXPOSE 8080`.
`CMD ["executable","param1","param2"]` (exec form, préféré) ou `CMD command param1 param2` (shell form) ou `CMD ["param1","param2"]` (comme paramètres par défaut pour `ENTRYPOINT`) : Spécifie la commande à exécuter par défaut au démarrage du conteneur. Il ne peut y avoir qu'une seule instruction `CMD` effective dans un Dockerfile (la dernière). Si l'utilisateur spécifie une commande à `docker run`, elle remplacera la `CMD`. Exemple : `CMD ["python", "app.py"]`. (Note : `ENTRYPOINT` est une autre instruction pour définir l'exécutable principal, souvent utilisée en combinaison avec `CMD` pour les paramètres par défaut).
Construire l'image : la commande `docker build`
Une fois votre Dockerfile écrit, il faut le transformer en une image utilisable. C'est le rôle de la commande `docker build`. Placez-vous dans le répertoire contenant votre Dockerfile et les éventuels fichiers à copier (comme notre `index.html`).
La syntaxe la plus courante est :
docker build -t nom_image:tag .
Détaillons :
`docker build` : La commande pour lancer le processus de construction.
`-t nom_image:tag` ou `--tag nom_image:tag` : L'option la plus importante. Elle permet d'attribuer un nom (tag) à l'image que vous construisez, sous la forme `nom_repository:tag_version`. C'est essentiel pour pouvoir y faire référence facilement plus tard (avec `docker run`, `docker push`, etc.). Choisissez un nom descriptif et un tag (souvent `latest` pour commencer, ou un numéro de version comme `1.0`). Exemple : `-t mon_nginx:1.0` ou `-t mon_app:latest`.
`.` (le point final) : C'est le contexte de build. Il indique à Docker où trouver le Dockerfile et les fichiers nécessaires (ceux référencés par `COPY` ou `ADD`). Le `.` signifie "le répertoire courant". Docker envoie l'ensemble de ce contexte (récursivement) à son démon pour effectuer la construction. Soyez conscient de la taille de ce contexte, car un envoi volumineux peut ralentir le build.
Exemple concret (en supposant que `Dockerfile` et `index.html` sont dans le répertoire courant) :
docker build -t mon_site_statique:v1 .
Docker va alors exécuter chaque instruction du Dockerfile, affichant la progression. Vous verrez les différentes étapes (`Step 1/4 : FROM ...`, `Step 2/4 : WORKDIR ...`, etc.). Si une étape échoue, le build s'arrête. Docker utilise un cache : si une instruction et ses dépendances n'ont pas changé depuis un build précédent, il réutilisera la couche en cache, accélérant considérablement les builds suivants.
Il est fortement recommandé d'utiliser un fichier `.dockerignore` (avec une syntaxe similaire au `.gitignore`) à la racine du contexte de build. Ce fichier liste les fichiers et répertoires à exclure du contexte envoyé au démon Docker (ex: `node_modules`, `.git`, fichiers temporaires, secrets). Cela allège le contexte, accélère le build et évite d'inclure des éléments inutiles ou sensibles dans l'image.
Lancer un conteneur à partir de votre image personnalisée
Félicitations ! Si la commande `docker build` s'est terminée avec succès (message `Successfully built ...` et `Successfully tagged ...`), vous disposez maintenant d'une image Docker personnalisée sur votre machine locale. Vous pouvez vérifier sa présence avec la commande `docker images`.
Pour utiliser cette image, rien de plus simple : employez la commande `docker run` que nous avons déjà vue, mais en spécifiant le nom et le tag que vous avez définis lors du build.
Reprenons notre exemple de site statique Nginx :
docker run -d -p 8888:80 --name site_perso mon_site_statique:v1
Ici :
`docker run` : Lance un conteneur.
`-d` : En mode détaché (arrière-plan).
`-p 8888:80` : Mappe le port 8888 de votre hôte au port 80 (exposé par `EXPOSE` et utilisé par Nginx) du conteneur.
`--name site_perso` : Donne un nom clair au conteneur.
`mon_site_statique:v1` : Indique à Docker d'utiliser l'image que nous venons de construire.
Vous devriez maintenant pouvoir accéder à `http://localhost:8888` dans votre navigateur et voir votre page `index.html` personnalisée, servie par Nginx depuis votre propre image Docker !
En créant un Dockerfile et en utilisant `docker build`, vous avez franchi une étape majeure. Vous pouvez désormais encapsuler vos applications et leurs dépendances dans des images portables et reproductibles, ouvrant la voie à des déploiements cohérents et simplifiés sur n'importe quelle machine disposant de Docker.