diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dc33713cf2cd3a958d9f7379da613be426c7fcc3..11c68d4315d153ef672b0031924458ac4be1cd0b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,8 @@ pages:
     - pages, docker
   image: boileaum/debian-jupyter
   script:
+    - pip3 --no-cache-dir install bash_kernel
+    - python -m bash_kernel.install
     - mkdir public
     - ./make_slides.sh
   artifacts:
diff --git a/README.md b/README.md
index c9adb36aeae2103a9e85403f4db0e8a879242d3d..70657cdd34d39e2c5902cf5b87fc01b60738458b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
 # TP GitLab CI
 
-## Execution du notebook jupyter
+Le contenu de cette session pratique est publié sur cette [page](https://boileau.pages.math.unistra.fr/tp-gitlab-ci/).
+
+### Execution du notebook jupyter
 
 - [Installer Anaconda](https://www.anaconda.com/download) pour disposer de `jupyter-notebook`
 - Ajouter le support du noyau bash :
diff --git a/pipeline.png b/pipeline.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1588d7a54c3efea874c59762d85ba94a5be8705
Binary files /dev/null and b/pipeline.png differ
diff --git a/tp-gitlab-ci.ipynb b/tp-gitlab-ci.ipynb
index 51fe7418db492e8702266a90eb76a4a9cadfa4f0..5a720fa5aebea923f58a8ca7e5c58df811ce0dfb 100644
--- a/tp-gitlab-ci.ipynb
+++ b/tp-gitlab-ci.ipynb
@@ -735,16 +735,199 @@
    "source": [
     "### Provoquez des erreurs\n",
     "\n",
-    "Modifier les fichiers\n",
+    "Modifier les fichiers pour provoquer des erreurs.\n",
+    "Notez que le test n'est pas exécuté si le build échoue."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
+   "source": [
+    "## Exo3 : Docker pour gérer les dépendances\n",
     "\n",
+    "À `helloworld`, on ajoute un programme en python `rosenbrock.py` qui calcule la [fonction de Rosenbrock](https://fr.wikipedia.org/wiki/Fonction_de_Rosenbrock). Ce programme nécessite [`pythran`](http://pythran.readthedocs.io/), un traducteur de python vers C++ qui permet d'accélérer le code python.\n",
+    "Cet exercice a pour but d'illustrer la gestion des dépendances de compilation avec les conteneurs Docker dans une chaîne GitLab CI.\n",
     "\n",
-    "- de build : notez que le test n'est pas exécuté\n",
-    "- de test : le\n"
+    "Pour le reproduire, il est nécessaire de :\n",
+    "1. créer un compte sur [DockerHub](https://hub.docker.com/)\n",
+    "2. y créer un dépôt vierge intitulé `rosen`\n",
+    "3. que Docker soit installé sur la machine qui héberge le runner (c'est le cas pour le runner partagé de ce TP)."
    ]
   },
   {
    "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "Dans votre clone local, basculez sur `exo3`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 68,
    "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "No local changes to save\n",
+      "Already on 'exo3'\n",
+      "Your branch is up-to-date with 'origin/exo3'.\n"
+     ]
+    }
+   ],
+   "source": [
+    "cd $TPDIR\n",
+    "git stash  # si vous avez des modifications non enregistrées dans exo2\n",
+    "git checkout exo3"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "### Entête et étape préliminaire du fichier `.gitlab-ci.yml`\n",
+    "\n",
+    "```yaml\n",
+    "stages:\n",
+    "  - build\n",
+    "  - test\n",
+    "  - release\n",
+    "\n",
+    "variables:\n",
+    "  CONTAINER_TEST_IMAGE: boileaum/rosen:$CI_COMMIT_REF_NAME\n",
+    "  CONTAINER_RELEASE_IMAGE: boileaum/rosen:latest\n",
+    "\n",
+    "before_script:\n",
+    "  - echo $DOCKERHUB_PASSWD | docker login -u boileaum --password-stdin\n",
+    "[...]\n",
+    "```\n",
+    "\n",
+    "Pour faire marcher l'exercice :\n",
+    "1. Remplacez `boileaum` par votre `username` sur DockerHub.\n",
+    "2. Sur GitLab dans `Settings > CI / CD > Secret variables`, créez une variable `DOCKERHUB_PASSWD` qui contient le mot de passe de votre compte DockerHub."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "### Etape de `build` :\n",
+    "\n",
+    "```yaml\n",
+    "[...]\n",
+    "b:docker:\n",
+    "  stage: build\n",
+    "  tags:\n",
+    "    - shell, docker\n",
+    "  script:\n",
+    "    - docker build --pull -t $CONTAINER_TEST_IMAGE -f ./docker/Dockerfile-rosen .\n",
+    "    - docker push $CONTAINER_TEST_IMAGE\n",
+    "[...]\n",
+    "```\n",
+    "\n",
+    "- on construit l'image de test à partir du fichier `./docker/Dockerfile-rosen`\n",
+    "- on pousse cette image sur DockerHub\n",
+    "\n",
+    "> **Note :** l'image `Dockerfile-rosen` est basée sur l'image construite avec `./docker/Dockerfile-pythran` et qui contient les dépendances."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "### Etapes de `test`\n",
+    "\n",
+    "```yaml\n",
+    "[...]\n",
+    "t:helloworld:\n",
+    "  stage: test\n",
+    "  tags:\n",
+    "    - shell, docker\n",
+    "  script:\n",
+    "    - docker pull $CONTAINER_TEST_IMAGE\n",
+    "    - docker run $CONTAINER_TEST_IMAGE /bin/bash -c 'python test_helloworld.py'\n",
+    "[...]\n",
+    "```\n",
+    "\n",
+    "- on tire l'image de test\n",
+    "- on lance les tests unitaires"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "### Etape de livraison\n",
+    "\n",
+    "```yaml\n",
+    "\n",
+    "[...]\n",
+    "r:docker:\n",
+    "  stage: release\n",
+    "  tags:\n",
+    "    - shell, docker\n",
+    "  script:\n",
+    "    - docker pull $CONTAINER_TEST_IMAGE\n",
+    "    - docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE\n",
+    "    - docker push $CONTAINER_RELEASE_IMAGE\n",
+    "  only:\n",
+    "    - exo3\n",
+    "[...]\n",
+    "```\n",
+    "\n",
+    "On ne fait que tirer l'image de test validée, la renommer, la pousser vers DockerHub."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "subslide"
+    }
+   },
+   "source": [
+    "### Pipeline complet\n",
+    "\n",
+    "\n",
+    "![](pipeline.png)\n",
+    "\n",
+    "Comme le montre ce schéma, l'exécution de différents jobs d'une même étape peut être réalisée en parallèle par différents runners.\n",
+    "\n",
+    "Plus d'information "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "slideshow": {
+     "slide_type": "slide"
+    }
+   },
    "source": [
     "## Récupérer un artifact"
    ]