diff --git a/README.md b/README.md
index 652e76be82a1be787da58dab3208683d6d1a663d..b5f829ccdbad8a281ec12bb88514b07f88ffebda 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
-# Lab: Configuration Management - Ansible and AWS
+# Lab: Configuration Management - Ansible
 
-Lab template for a CM/deployment exercise with Ansible and AWS.
+Lab template for a CM/deployment exercise with Ansible and AWS. Originally
+written by Marcel Graf (HEIG-VD).
 
 ## Pedagogical objectives ##
 
@@ -24,10 +25,10 @@ conventions about the *command line prompt*:
   * `ins`: your VM instance
 
 
-### Task 1: install Ansible ###
+### Task #1: install Ansible ###
 
-In this task you will install [Ansible](https://www.ansible.com/) on your
-local machine. Please, refer to your [OS
+**Goal:** install [Ansible](https://www.ansible.com/) on your local
+machine. Please, refer to your [OS
 documentation](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html)
 for the proper way to do so.
 
@@ -37,62 +38,63 @@ Once done, verify that Ansible is installed correctly by running:
 lcl$ ansible --version
 ```
 
-### Task 2: Create a VM on Amazon Web Services ###
+### Task #2: Create a VM on a Cloud of your choice ###
 
-In this task you will create a VM on Amazon Web Services that will be managed
-by Ansible.
+**Goal:** create a VM that will be managed by Ansible. Chose any Cloud you are
+familiar with, then:
 
+  1. Import or create an RSA key pair for SSH access to the VM.
 
-  1. Switch the AWS console to the N. Virginia region to avoid resource
-  limitations -- see "Zones" in the "Account attributes" pane.
+  1. Create a security group/policy that allows incoming SSH, HTTP and HTTPS
+  traffic from anywhere (0.0.0.0/0).
 
-  2. Import or create an RSA key pair in this region and download the private key.
-
-  3. It it doesn't exist yet, create a security group named `Lab-Ansible-AWS`
-  group that allows incoming SSH, HTTP and HTTPS traffic from anywhere
-  (0.0.0.0/0).
-
-  4. Create an EC2 instance with the following characteristics (all other
-     parameters at default value):
+  1. Create a VM instance with the following characteristics:
     - OS: Ubuntu Server 20.04 LTS
-    - type: t2.micro
-    - security group: Lab-Ansible-AWS
+    - type: the smallest capable of running the above OS. 1 core, 1GB RAM,
+      10GB virtual disk should be enough.
+    - security group/policy: the one you created above
     - key pair: the one you created above
 
-After launching make sure you can SSH into the VM using your private key
-(`<your-private-key>` is a full path):
+After launching make sure you can SSH into the VM using your
+`<your-private-key>` (must be a full path):
 
 ``` shell
 lcl$  ssh -i <your-private-key> ubuntu@<VM-DNS-name-or-IP-address>
 ```
 
-### Task 3: Configure Ansible to connect to the managed VM ###
-
-In this task you will tell Ansible about the machines it shall manage.
+### Task #3: Configure Ansible to connect to the managed VM ###
 
-Create a "sandbox" directory on your local machine f.i. `~/ansible/playbooks`,
-and create a file called `hosts.yml` which will serve as the inventory file,
-and add the following:
+**Goal:** intruct Ansible about the machines (hosts) it shall manage.
 
-@@@ RESTART FROM HERE @@@
+Create a "sandbox" directory on your local machine f.i. `~/ansible/`, and
+create a file called `hosts.yml` which will serve as the *inventory* file
+(a.k.a. hostfile; it can also be written in `.ini` style), and add the
+following:
 
 ``` yaml
-testserver ansible_ssh_host=<VM-DNS-name-or-IP-address>
-    ansible_user=ubuntu
-    ansible_ssh_private_key_file=<your-private-key>
+all:
+  hosts:
+    testserver:
+      ansible_ssh_host: <VM-DNS-name-or-IP-address>
+      ansible_user: ubuntu
+      ansible_ssh_private_key_file: <your-private-key>
 ```
 
-Verify that you can use Ansible to connect to the server:
+and check its validity:
 
-ansible testserver -i hosts -m ping
+``` shell
+ansible-inventory -i ~/ansible/hosts.yml --list
+```
 
-You should see output similar to the following:
+Verify that you can use Ansible to connect to the testserver:
 
-testserver | SUCCESS => {
-    "changed": false,
-    "ping": "pong"
-}
+``` shell
+lcl$ ansible -i ~/ansible/hosts.yml testserver -m ping
+```
 
+You should see output similar to the following:
+
+`
 testserver | SUCCESS => {
     "ansible_facts": {
         "discovered_interpreter_python": "/usr/bin/python3"
@@ -100,35 +102,264 @@ testserver | SUCCESS => {
     "changed": false,
     "ping": "pong"
 }
+`
 
-We can now simplify the configuration of Ansible by using an ansible.cfg file which allows us to set some defaults.
-
-In the playbooks directory create the file ansible.cfg:
+Let's simplify the configuration of Ansible by using a user default
+configuration file `~/.ansible.cfg` with contents (`.ini` style):
 
+``` ini
 [defaults]
-inventory = hosts
+inventory = ~/ansible/hosts.yml
 remote_user = ubuntu
-private_key_file = <path to keyfile.pem>
+private_key_file = <your-private-key>
 host_key_checking = false
 deprecation_warnings = false
+```
+
+Notice that SSH's host key checking is disabled for convenience, as the
+managed VM will get a new IP address each time it is recreated. **For
+production systems this is be a security risk!** [See the
+doc](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#the-configuration-file)
+for the other details.
+
+With the above default settings our inventory file now simplifies to:
+
+``` yaml
+all:
+  hosts:
+    testserver:
+      ansible_ssh_host: <VM-DNS-name-or-IP-address>
+```
 
-Among the default options we also disable SSH's host key checking. This is convenient when we distroy and recreate the managed server (it will get a new host key every time). In production this may be a security risk.
+Now calling ansible is simpler:
 
-We also disable warnings about deprecated features that the 2.x version of Ansible emits.
+``` shell
+lcl$ ansible testserver -m ping
+```
 
-With these default values the hosts inventory file now simplifies to:
+The ansible command can be used to run arbitrary commands on the remote
+machines. F.i., to execute the uptime command:
 
-testserver ansible_ssh_host=<managed VM's public IP address>
+``` shell
+lcl$ ansible testserver -m command -a uptime
+testserver | CHANGED | rc=0 >>
+ 09:05:10 up 54 min,  2 users,  load average: 0.01, 0.00, 0.00
+```
 
-We can now run Ansible again and don't need to specify the inventory file any more:
 
-ansible testserver -m ping
+### Task #4: Install web application ###
 
-The ansible command can be used to run arbitrary commands on the remote machines. Use the -m command option and add the command in the -a option. For example to execute the uptime command:
+**Goal:** configure the managed host to run a simple Web application served by
+the nginx server. This necessitates four files:
 
-ansible testserver -m command -a uptime
+  1. The inventory file `~/ansible/hosts.yml` as written before.
+  2. A "playbook" that specifies what to configure
+     `~/ansible/playbooks/web.yml`.
+  3. The nginx's configuration file `~/ansible/playbooks/files/nginx.conf`.
+  4. A home page template for our Web site
+     `~/ansible/playbooks/templates/index.html.j2`.
 
-You should see output similar to this:
+To make our playbook more generic, we will use an ansible group called
+`webservers` holding our managed server, so that we can then later easily add
+more servers which Ansible will configure identically.  Modify the hostfile by
+adding a definition of the group webservers, this is **left to you as an
+exercise**. If you do it correctly, you should get the following inventory
+list:
 
-testserver | CHANGED | rc=0 >>
- 18:56:58 up 25 min,  1 user,  load average: 0.00, 0.01, 0.02
+``` shell
+lcl$ ansible-inventory  --list
+{
+    "_meta": {
+        "hostvars": {
+            "testserver": {
+                "ansible_ssh_host": ...
+            }
+        }
+    },
+    "all": {
+        "children": [
+            "ungrouped",
+            "webservers"
+        ]
+    },
+    "webservers": {
+        "hosts": [
+            "testserver"
+        ]
+    }
+}
+```
+
+You should now be able to ping the webservers group:
+
+``` shell
+lcl$ ansible webservers -m ping
+```
+
+The output should be the same as before.
+
+Now, create a playbook named `~/ansible/playbooks/web.yml` with the following content:
+
+``` yaml
+---
+- name: Configure webserver with nginx
+  hosts: webservers
+  become: True
+  tasks:
+    - name: install nginx
+      apt: name=nginx update_cache=yes
+
+    - name: copy nginx config file
+      copy: src=files/nginx.conf dest=/etc/nginx/sites-available/default
+
+    - name: enable configuration
+      file: >
+        dest=/etc/nginx/sites-enabled/default
+        src=/etc/nginx/sites-available/default
+        state=link
+
+    - name: copy index.html
+      template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644
+
+    - name: restart nginx
+      service: name=nginx state=restarted
+```
+
+:question: How many tasks are declared in the above playbook?
+
+Then, create the nginx's configuration file
+`~/ansible/playbooks/files/nginx.conf` referenced in the playbook references,
+which the following content:
+
+``` nginx
+server {
+    listen 80 default_server;
+    listen [::]:80 default_server ipv6only=on;
+
+    root /usr/share/nginx/html;
+    index index.html index.htm;
+
+    server_name localhost;
+
+    location / {
+        try_files $uri $uri/ =404;
+    }
+}
+```
+
+As per the aboveconfiguration file, nginx will serve the Web app's homepage
+from `index.html`. This will be generated via Ansible's templating engine from
+a template, which has to be created as
+`~/ansible/playbooks/templates/index.html.j2` and should hold the following:
+
+``` html
+<html>
+  <head>
+    <title>Welcome to ansible</title> </head>
+  <body>
+    <h1>nginx, configured by Ansible</h1>
+    <p>If you can see this, Ansible successfully installed nginx.</p>
+    <p>{{ ansible_managed }}</p>
+    <p>Some facts Ansible gathered about this machine:
+    <table>
+      <tr><td>OS family:</td><td>{{ ansible_os_family }}</td></tr>
+      <tr><td>Distribution:</td><td>{{ ansible_distribution }}</td></tr>
+      <tr><td>Distribution version:</td><td>{{ ansible_distribution_version }}</td></tr>
+    </table>
+    </p>
+  </body>
+</html>
+```
+
+Now, run the newly created playbook to install and configure nginx, and to
+deploy the Web app on the managed host:
+
+``` shell
+lcl$ ansible-playbook ~/ansible/playbooks/web.yml
+```
+
+If everything goes well, the last output lines should be like:
+
+    PLAY [Configure webserver with nginx] **************************************
+
+    TASK [Gathering Facts] *****************************************************
+    ok: [testserver]
+
+    TASK [install nginx] *******************************************************
+    changed: [testserver]
+
+    TASK [copy nginx config file] ***********************************************
+    changed: [testserver]
+
+    TASK [enable configuration] *************************************************
+    ok: [testserver]
+
+    TASK [copy index.html] ******************************************************
+    changed: [testserver]
+
+    TASK [restart nginx] ********************************************************
+    changed: [testserver]
+
+    PLAY RECAP ******************************************************************
+    testserver        : ok=6    changed=4    unreachable=0    failed=0
+
+
+Point your Web browser to the address of the managed server (mind that we are
+not using SSL): `http://<VM-DNS-name-or-IP-address>`. You should see the
+homepage showing "nginx, configured by Ansible".
+
+
+### Task #5: Test Desired State Configuration principles ###
+
+**Goal:** test and verify that Ansible implements the principles of Desired
+State Configuration.
+
+According to this principle, before doing anything, Ansible should establish
+the current state of the managed server, compare it to the desired state
+expressed in the playbook, and then only perform the actions necessary to
+bring the current state to the desired state.
+
+In its ouput, Ansible marks tasks where it had to perform some action as
+*changed* whereas tasks where the actual state already corresponded to the
+desired state as *ok*.
+
+  1. Return to the output of the last ansible command that run the `web.yml`
+     playbook.
+     1. :question: There is one additional task that was not in the
+     playbook. Which one?
+     1. :question: Among the tasks that are in the playbook there is
+     one task that Ansible marked as *ok*. Which one? Do you have a possible
+     explanation?
+  1. Re-run the `web.yml` playbook a second time and compare its output with
+     the first run's. In principle nothing should have changed.
+     1. :question: Which tasks are marked as changed? Why?
+  1. SSH into the managed server. Modify the nginx configuration file
+     `default` (by the way, what's the deployment path?), for example by
+     adding a line with a comment. Re-run the playbook.
+     1. :question: What does Ansible do to the file and what does it show in
+     its output?
+  1. Do something more drastic like completely removing the homepage
+     `index.html` (by the way, what's the deployment path?) and repeat the
+     previous question.
+     1. :question: What happened this time?
+
+
+### Task #6: Adding a handler for nginx restart ###
+
+**Goal:** improve the playbook by restarting nginx only when needed.
+
+The current version of the playbook restarts nginx every time the playbook is
+run, irrespective of the managed server's state. This goes indeed a bit too far.
+
+By putting the nginx restart command into a *handler*, instead of a task, its
+execution can be made conditional. The rationale is that nginx is restarted
+only if one of the tasks that affects nginx's configuration resulted in a
+change.
+
+Consult the [Ansible documentation about
+handlers](https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html). Modify
+the playbook so that the nginx restart becomes a handler and the tasks that
+potentially modify its configuration use *notify* to call the handler when
+needed.
+
+Copy the modified playbook into the lab report.