Conteneurs et réseaux
Objectifs de cet atelier pratique
- 
Comprendre l'interaction de Docker avec le noyau Linux pour contrôler le trafic réseau 
- 
Comprendre le réseau par défaut utilisé pour les conteneurs 
- 
Inspecter les réseaux Docker 
- 
Déterminer l'adresse IP d'un conteneur 
- 
Créer des réseaux personnalisés qui permettent de séparer des groupes de conteneurs 
- 
Dockeriser une application web PHP comme WordPress 
- 
Utiliser le serveur DNS intégré dans Docker 
Docker et le pare-feu Linux
Docker utilise le pare-feu Netfilter intégré au noyau Linux pour toutes les
opérations de pare-feu et de routage. Pour manipuler et configurer le pare-feu,
on utilise couramment iptables.
L'outil iptables est progressivement remplacé par nftables.
Lancez un conteneur et regardez ce qui se passe sous le capot :
$ docker run -dit -p 8080:80 php:apache
71c2e5a3ab89984a1eca0fdf9496650b9fc9b45c1e498170356f4ff35c1c28a3
Comme son nom le suggère, l'image php:apache contient le serveur Web Apache
avec le module mod_php. Si vous souhaitez dockeriser une application web
écrite en PHP, c'est un bon point de départ.
Vérifions que le conteneur est en état de marche et que Docker a ouvert le port 8080 sur le système hôte pour le faire pointer vers le port 80 du conteneur :
Affichez la chaîne personnalisée DOCKER avec iptables :
$ sudo iptables -nL "DOCKER"
Chain DOCKER (1 references)
target     prot opt source       destination
ACCEPT     tcp  --  0.0.0.0/0    172.17.0.2       tcp dpt:80
L'info tcp dpt:80 signifie que le port TCP 80 est le port de destination.
Docker applique une adresse IP locale (c'est-à-dire non-routable sur Internet)
à notre conteneur.
Essayez d'accéder au serveur web Apache par le biais de cette adresse :
$ curl http://172.17.0.2
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.57 (Debian) Server at 172.17.0.2 Port 80</address>
</body></html>
Pour avoir une première idée du fonctionnement du réseau sous Docker, connectez-vous maintenant à l'adresse IP de l'hôte sur le port TCP 8080 :
$ curl http://127.0.0.1:8080
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.57 (Debian) Server at 127.0.0.1 Port 8080</address>
</body></html>
Le routage interne de Docker fait transiter le trafic de manière transparente.
Arrêtez ce conteneur :
Utiliser le réseau de l'hôte
Dans certains cas de figure, il peut arriver que vous souhaitez désactiver la mise en réseau sophistiquée de Docker.
Sécurité
Cette manière de procéder est peu commune, étant donné qu'elle n'est pas du tout sécurisée. Le conteneur et le système hôte ne sont pas isolés dans ce scénario. Quoi qu'il en soit, sachez que cette possibilité existe.
Vous pouvez activer cette fonctionnalité en utilisant l'option --network host
dans la commande docker run. Cette option permet au conteneur de partager le
réseau de l'hôte. Le conteneur n'a pas d'adresse IP qui lui est propre.
$ docker run -dit --network host php:apache
e68ad367f3e986af0c754c1c9445135a1558bf7c54578edfb17f50199ffb5e59
Si nous jetons un œil aux chaînes personnalisés d'iptables, nous voyons
qu'aucun routage n'est effectué :
Voyons si nous pouvons accéder au serveur web qui tourne dans ce conteneur :
$ curl http://127.0.0.1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.57 (Debian) Server at 127.0.0.1 Port 80</address>
</body></html>
Là aussi, faisons un peu de ménage avant d'aller plus loin.
Le réseau bridge par défaut
Si vous utilisez un orchestrateur, vous n'aurez probablement pas à vous soucier de la façon dont Docker gère la mise en réseau sous le capot. En revanche, c'est toujours utile à savoir si vous devez plonger les mains dans le cambouis en cas de dysfonctionnement.
Orchestrateur ?
Et si vous ne savez pas ce que c'est qu'un orchestrateur, ne vous tracassez pas. Cela ne vous empêchera pas de vivre pour l'instant.
Pour afficher les réseaux utilisés par Docker, invoquez la commande docker
network ls :
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
8c5e2a8c9201   bridge    bridge    local
a12b2a39c25b   host      host      local
40756a45b4dd   none      null      local
- 
Le premier réseau affiché ici est le réseau bridge. C'est le réseau par défaut. Si vous ne spécifiez pas explicitement un réseau pour un conteneur donné, il utilisera le réseau nommébridge.
- 
Sans trop compliquer les choses, un bridge ou pont est un dispositif qui permet de relier deux réseaux. 
- 
Concrètement, si vous avez un conteneur avec un serveur web attaché au réseau par défaut bridgeet un conteneur avec un serveur de bases de données attaché à ce même réseaubridge, ils seront capables de communiquer entre eux.
- 
Les deux autres réseaux répertoriés par docker network lsrelient les piles réseau du conteneur et du système hôte. Vous pouvez sereinement faire abstraction de ces deux réseaux.
- 
La chose la plus importante à garder en tête ici est que le réseau nommé bridgeest le réseau utilisé par défaut pour les conteneurs, à moins que vous ne spécifiez explicitement autre chose.
Pour en savoir plus sur un réseau donné, invoquez la commande docker network
inspect :
$ docker network inspect bridge
...
  "Containers": {},
  "Options": {
    "com.docker.network.bridge.default_bridge": "true",
    "com.docker.network.bridge.enable_icc": "true",
    "com.docker.network.bridge.enable_ip_masquerade": "true",
    "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
    "com.docker.network.bridge.name": "docker0",
    "com.docker.network.driver.mtu": "1500"
  },
La section Containers vous permet d'afficher le ou les conteneurs qui
utilisent actuellement le pont pour se connecter à la pile réseau du système
hôte, ainsi que les adresses IP associées :
$ docker run -dit --name webserver1 php:apache
09d1724677fd99f8bdf984336b4a88d638e5882b5b6f5...
$ docker network inspect bridge
...
  "Containers": {
      "09d1724677fd99f8bdf984336b4a88d638e58...": {
      "Name": "webserver1",
      "EndpointID": "c61a0f9906cafc3a5d703...",
      "MacAddress": "02:42:ac:11:00:02",
      "IPv4Address": "172.17.0.2/16",
      "IPv6Address": ""
    }
  },
Ici vous voyez les détails du conteneur webserver1 qui utilise le réseau
bridge. Repérez l'adresse MAC du conteneur ainsi que son adresse IP.
Démarrez un autre conteneur :
$ docker run -dit --name webserver2 php:apache
99a76a5bb45422031f274b194b6d966545f0a850ab4677...
$ docker network inspect bridge
...
  "Containers": {
      "09d1724677fd99f8bdf984336b4a88d638e58...": {
      "Name": "webserver1",
      "EndpointID": "c61a0f9906cafc3a5d703...",
      "MacAddress": "02:42:ac:11:00:02",
      "IPv4Address": "172.17.0.2/16",
      "IPv6Address": ""
    }
      "99a76a5bb45422031f274b194b6d966545f0a8...": {
      "Name": "webserver2",
      "EndpointID": "7b34786eda8133913a308d...",
      "MacAddress": "02:42:ac:11:00:03",
      "IPv4Address": "172.17.0.3/16",
      "IPv6Address": ""
    }
  },
Repérez le conteneur webserver2 qui utilise également le réseau bridge,
avec son adresse MAC et son adresse IP.
À présent, nous pouvons vérifier si les conteneurs du réseau bridge sont
effectivement capables de communiquer entre eux. Pour ce faire, nous allons
ouvrir un shell dans l'un des conteneurs et contacter le serveur web qui
tourne sur l'autre.
$ docker exec -it webserver1 bash
root@09d1724677fd:/var/www/html# apt update
root@09d1724677fd:/var/www/html# apt install -y iproute2
root@09d1724677fd:/var/www/html# ip addr
...
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue ...
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@09d1724677fd:/var/www/html# curl http://172.17.0.3
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.57 (Debian) Server at 172.17.0.3 Port 80</address>
</body></html>
root@b843c944dd55:/var/www/html# exit
Afficher l'adresse IP sans la commande ip
Lorsque la commande ip n'est pas disponible sur un conteneur, vous pouvez
essayer de vous servir de la commande hostname -I pour afficher son
adresse IP.
Affichons les logs générés par le conteneur webserver2 :
$ docker logs webserver2
...
172.17.0.2 - - [08/Jul/2023:05:35:45 +0000] "GET / HTTP/1.1" 403 436 "-" 
"curl/7.88.1"
Important
Ne supprimez pas les deux conteneurs webserver1 et webserver2.
Laissez-les tourner pour l'instant. Nous en aurons besoin tout à l'heure
pour effectuer un petit test.
Isoler un réseau pour une application
Prenons un cas de de figure concret. Admettons que vous souhaitez créer un réseau isolé pour un groupe de deux conteneurs qui vont héberger un blog. Un des conteneurs fera office de serveur web, l'autre va fournir un serveur de bases de données. Le serveur web doit pouvoir communiquer avec le serveur de bases de données, mais aucun autre conteneur ne doit pouvoir y accéder. De même, aucune machine extérieure au système hôte Docker ne doit pouvoir communiquer avec la base de données. Autrement dit, la base de données ne doit pas être accessible au public.
Pour ce faire, nous allons créer un réseau pour notre blog en y attachant les deux conteneurs :
Le nouveau réseau blog est de type bridge :
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
3cc5986cb45e   blog      bridge    local
97bab690e648   bridge    bridge    local
a12b2a39c25b   host      host      local
40756a45b4dd   none      null      local
Nous allons lancer un conteneur basé sur l'image php:apache en l'assignant au
réseau blog. Mais avant de faire ça, nous allons créer un volume pour stocker
les pages web de ce conteneur :
$ docker volume create blog_web_data
blog_web_data
$ docker run -dit --name web --network blog -p 80:80 \
  --mount src=blog_web_data,dst=/var/www/html php:apache
cde718794ee225e952330cc4acf306615eb89e790ba26c83f884a8e40de120bc
Inspectons notre réseau blog :
$ docker network inspect blog
...
  "Containers": {
    "cde718794ee225e952330cc4acf306615...": {
      "Name": "web",
      "EndpointID": "efd4ba429ed678d5c3fcd5f8...",
      "MacAddress": "02:42:ac:12:00:02",
      "IPv4Address": "172.18.0.2/16",
      "IPv6Address": ""
    }
  },
Pour montrer que les conteneurs d'un réseau ne peuvent pas communiquer avec les
conteneurs d'un autre réseau, attachons-nous au conteneur webserver1 sur le
réseau bridge et essayons d'accéder au conteneur qui tourne sur le réseau
blog :
$ docker exec -it webserver1 bash
root@8f891d85f3fb:/var/www/html# curl -m 5 http://172.18.0.2
curl: (28) Connection timed out after 5001 milliseconds
root@8f891d85f3fb:/var/www/html# exit
Timeout
L'option -m 5 définit une attente maximale de cinq secondes pour curl.
Nous avons effectivement réussi à isoler notre conteneur web en l'assignant à
un réseau distinct.
La prochaine étape consiste à créer un conteneur qui fera office de serveur de bases de données pour notre blog. Dans notre cas pratique, nous allons utiliser l'image du serveur de bases de données MariaDB.
MySQL vs. MariaDB
MariaDB est un fork libre du serveur de bases de données MySQL.
L'image mariadb utilise un volume pour le répertoire /var/lib/mysql. Cette
info nous est fournie par la documentation de l'image sur Docker Hub. En
particulier, repérez la ligne suivante dans le Dockerfile utilisé pour
construire l'image :
Ici, nous avons le choix :
- 
Nous pouvons créer notre propre volume et le monter sur /var/lib/mysql.
- 
Alternativement, nous pouvons simplement démarrer le conteneur et Docker se charge de créer un volume pour nous en le montant sur /var/lib/mysql.
Nous allons opter pour la première solution et créer un volume personnalisé, ce qui nous permet de lui donner un nom parlant et de faire les choses plus clairement :
À présent nous disposons de deux volumes, un pour le conteneur web, l'autre
pour le conteneur database :
Retournez sur la documentation de MariaDB sur Docker Hub et repérez la section Environment Variables. En résumé, vous devez fournir au moins une variable d'environnement au lancement du conteneur.
Nous allons utiliser l'option -e conjointement avec la commande docker run
pour définir la variable d'environnement MYSQL_ROOT_PASSWORD, en spécifiant
le mot de passe root à utiliser. Lorsque le conteneur démarre, il définira le
mot de passe root en conséquence.
Si nous définissons la variable d'environnement MYSQL_DATABASE, le conteneur
va créer une base de données avec le nom fourni en argument au lancement. Nous
allons en profiter pour créer une base de données nommée wordpress :
$ docker run -dit --name database --network blog \
  -e MYSQL_ROOT_PASSWORD=password123 -e MYSQL_DATABASE=wordpress \
  --mount src=blog_db_data,dst=/var/lib/mysql mariadb
08ca4854ab6fd7cec74c61c1c988dc943b099bd5687e19d2029fc24167b8943f
Exposer le port MySQL ?
Notez bien que nous n'avons pas utilisé l'option -p pour publier le
port MySQL 3306. Nous ne souhaitons pas que ce port soit publiquement
accessible. Tout ce que nous voulons, c'est que le serveur web puisse y
accéder.
À présent, inspectons notre réseau blog :
$ docker network inspect blog
...
  "Containers": {
    "3bc4c3eb1bc814bc6e2bb70a15682c1ea9c7649...": {
      "Name": "web",
      "EndpointID": "efd4ba429ed678d5c3fcd5...",
      "MacAddress": "02:42:ac:12:00:02",
      "IPv4Address": "172.18.0.2/16",
      "IPv6Address": ""
},
    "793c643328a9019f45fd8d02dbf8bca50039a7d...": {
      "Name": "database",
      "EndpointID": "ae17989e2b6388b09ff90...",
      "MacAddress": "02:42:ac:12:00:03",
      "IPv4Address": "172.18.0.3/16",
      "IPv6Address": ""
    }
  },
En passant, jetons un œil sur le volume utilisé pour le conteneur
database :
$ docker volume inspect blog_db_data | grep Mountpoint
"Mountpoint": "/var/lib/docker/volumes/blog_db_data/_data",
$ sudo ls -1F /var/lib/docker/volumes/blog_db_data/_data
aria_log.00000001
aria_log_control
ddl_recovery.log
ib_buffer_pool
ibdata1
ib_logfile0
ibtmp1
mariadb_upgrade_info
multi-master.info
mysql/
performance_schema/
sys/
undo001
undo002
undo003
wordpress/
- 
Voilà l'ensemble des données créées et utilisées par le serveur de bases de données. Si le conteneur databasevenait à être détruit, les données générées resteraient persistantes dans ce volume. Ce qui signifie que nous pourrions démarrer un autre conteneur avec le même point de montage, et nos données seraient préservées.
- 
Notez aussi qu'aucune des variables d'environnement comme MYSQL_ROOT_PASSWORDouMYSQL_DATABASEn'aura d'effet si le répertoire de données contient déjà une base de données. Autrement dit, une base de données préexistante sera toujours gardée en l'état au lancement du conteneur.
Voyons si nous pouvons nous connecter au serveur de bases de données depuis le serveur web. Pour ce faire, nous devons installer un client MySQL :
$ docker exec -it web bash
root@3bc4c3eb1bc8:/var/www/html# apt update
root@3bc4c3eb1bc8:/var/www/html# apt install -y default-mysql-client
root@c7efa289fa0c:/var/www/html# mysql -h 172.18.0.3 -p
Enter password: ***********    <-- password123
Welcome to the MariaDB monitor.
...
MariaDB [(none)]> status;
...
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| wordpress          |
+--------------------+
4 rows in set (0.003 sec)
MariaDB [(none)]> quit;
Bye
root@c7efa289fa0c:/var/www/html# exit
Un aspect fort pratique des réseaux personnalisés, c'est que Docker fournit un
serveur DNS embarqué. Ce qui veut dire que nous pouvons très bien accéder aux
conteneurs en utilisant leur nom. Concrètement, cela nous évite la corvée
d'avoir à déterminer l'adresse IP du conteneur database, puisque nous pouvons
directement le contacter en utilisant son nom :
$ docker exec -it web bash
root@c7efa289fa0c:/var/www/html# mysql -h database -p
Enter password: ********
Welcome to the MariaDB monitor.
...
C'est vraiment la meilleure façon de procéder, étant donné que Docker peut très bien assigner une adresse IP différente au conteneur lorsque celui-ci redémarre. Il vaut donc mieux l'adresser en utilisant son nom et laisser Docker gérer la correspondance entre le nom et l'adresse IP.
Pas de DNS dans le réseau bridge par défaut
Dans l'état actuel des choses, le DNS de Docker fonctionne uniquement avec
les réseaux personnalisés, mais pas avec le réseau bridge par défaut.
Par ailleurs, le DNS ne fonctionne pas non plus à travers des réseaux
cloisonnés, ce qui veut dire que vous ne pourrez pas résoudre le nom d'un
conteneur qui se trouve sur un réseau différent.
Continuons dans la mise en place de notre blog. Nous allons utiliser WordPress,
qui est écrit en PHP, et c'est pourquoi nous utilisons l'image PHP
correspondante. WordPress doit pouvoir se connecter à la base de données, ce
qui requiert l'extension PHP mysqli correspondante.
Heureusement pour nous, le conteneur php nous permet de faire cela assez
facilement. Là encore, jetez un œil à la documentation de l'image et
repérez la section How to install more PHP extensions :
La documentation nous explique l'utilisation de ces scripts dans un
Dockerfile, et c'est ce que nous ferions en temps normal.
Ici, nous avons déjà lancé notre conteneur, et nous allons installer
manuellement l'extension mysqli à des fins de démonstration :
$ docker exec -it web bash
root@cde718794ee2:/var/www/html# docker-php-ext-install mysqli
Configuring for:
PHP Api Version:         20220829
Zend Module Api No:      20220829
Zend Extension Api No:   420220829
...
Libraries have been installed in:
/usr/src/php/ext/mysqli/modules
Maintenant que l'extension mysqli est installée, nous devons redémarrer le
serveur web Apache. Pour ce faire, il suffit d'arrêter et de redémarrer le
conteneur :
Voyons si tout se passe comme prévu :
$ docker top web
UID   PID    PPID   C  STIME  TTY    TIME      CMD
root  56247  56225  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
33    56288  56247  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
33    56289  56247  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
33    56290  56247  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
33    56291  56247  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
33    56292  56247  0  08:29  pts/0  00:00:00  apache2 -DFOREGROUND
Effectivement, nous voyons une série de processus apache2 en train de tourner
dans le conteneur.
Nous pouvons passer à l'installation de WordPress. Dans un premier temps, nous allons télécharger et décompresser WordPress dans le volume du conteneur correspondant. Puis nous allons nous connecter au conteneur pour définir les permissions qui vont bien :
$ docker volume inspect blog_web_data | grep Mountpoint
"Mountpoint": "/var/lib/docker/volumes/blog_web_data/_data",
$ su -
Mot de passe : **********
# cd /var/lib/docker/volumes/blog_web_data/_data
# wget -c https://wordpress.org/wordpress-latest.tar.gz
# ls
wordpress-latest.tar.gz
# tar -xzf wordpress-latest.tar.gz
# ls
wordpress  wordpress-latest.tar.gz
# rm -f wordpress-latest.tar.gz
# exit
logout
$ docker exec -it web bash
root@cde718794ee2:/var/www/html# ls
wordpress
root@cde718794ee2:/var/www/html# chown -R www-data:www-data wordpress/
root@cde718794ee2:/var/www/html# exit
À présent, ouvrez l'installateur de WordPress dans un navigateur web :
- 
Langue : Français 
- 
Nom de la base de données : wordpress
- 
Identifiant : root
- 
Mot de passe : password123
- 
Adresse de la base de données : database(nom du conteneur correspondant)
- 
Préfixe des tables : wp_
Lancez l'installation en choisissant un utilisateur WordPress et un nom correspondant. Si tout se passe bien, le tableau de bord de WordPress s'affiche dans le navigateur :
Pourquoi faire simple quand on peut faire compliqué ?
Même si Docker facilite énormément les choses par rapport à une
installation manuelle classique de WordPress, nous aurions pu nous y
prendre de façon beaucoup plus simple en optant directement pour l'image
wordpress toute faite. Si nous avons procédé comme nous l'avons fait,
c'est surtout pour illustrer par la pratique une série de concepts comme
l'isolation des réseaux, le fonctionnement du DNS sous Docker, les
variables d'environnement, etc.
À vous de jouer !
Challenge n° 1
- 
Arrêtez et supprimez le conteneur web.
- 
Rafraîchissez la page de votre blog. Vous constatez que WordPress ne s'affiche plus. 
- 
Écrivez un Dockerfilesimple pour construire une image PHP qui intègre l'extensionmysqli.
- 
Construisez l'image <votre_identifiant>/php:mysqlià partir de ceDockerfile.
- 
Publiez cette image sur Docker Hub. 
- 
Utilisez l'image <votre_identifiant>/php:mysqlipour relancer votre blog WordPress.
- 
Arrêtez et supprimez tous les conteneurs. 
- 
Faites le ménage dans les volumes et les réseaux personnalisés. 
Challenge n° 2
- 
Dans cet atelier pratique, vous allez installer le système de gestion de contenu Drupal comme vous l'avez fait avec WordPress. 
- 
Créez un réseau isolé drupal.
- 
Créez un volume dbqui contiendra les données du serveur de bases de données.
- 
Récupérez la dernière image de PostgreSQL. 
- 
Ouvrez la documentation de l'image PostgreSQL sur Docker Hub et repérez les variables d'environnement pour le mot de passe, l'utilisateur et le nom de la base de données. 
- 
Lancez un conteneur nommé dbet basé sur PostgreSQL. Intégrez-le au réseaudrupal. Montez le volumedbsur le répertoire/var/lib/postgresql/datadu conteneur. Créez une base de donnéesdrupalexploitée par l'utilisateurdrupalavec le mot de passepassword123.
- 
Inspectez le réseau drupalpour vous assurer que le conteneurdby est bien connecté.
- 
Récupérez la dernière image de l'application Drupal. 
- 
Lancez un conteneur nommé drupalet basé sur Drupal. Intégrez-le au réseaudrupal. Publiez le port 80 du conteneur et mappez-le vers le port 80 du système hôte.
- 
Inspectez le réseau drupalet repérez vos deux conteneursdbetdrupal.
- 
Ouvrez un navigateur web à l'adresse http://localhost. 
- 
Lancez l'installation de Drupal. Sélectionnez le profil d'installation Standard et la langue anglaise. Renseignez les paramètres de connexion à votre base PostgreSQL. Affichez les options avancées pour renseigner l'hôte PostgreSQL. 
- 
L'application Drupal met quelques minutes à s'installer. 
- 
Si le tableau de bord de Drupal s'affiche, vous pouvez considérer que vous avez réussi le challenge. 



