diff --git a/.gitignore b/.gitignore
index 9fadef5a83ce29b8f9130f13dc1948f052878305..8997722737be790c0a5835861112f0fe024420d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
 /terraform.tfstate
 /terraform.tfstate.backup
 
+/id_rsa
+/id_rsa.pub
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 514d96f1a657030958ae23b630cf578d0efb481b..85ce6e3426aee3d6c8c8ac3c4ae7726096c5d71e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,6 +22,8 @@ validate:
     - linux
     - small
   extends: .terraform:validate
+  before_script:
+    - cp $SSH_PRIVATE_KEY id_rsa
 
 build:
   tags:
@@ -30,6 +32,8 @@ build:
   extends: .terraform:build
   environment:
     name: $TF_STATE_NAME
+  before_script:
+    - cp $SSH_PRIVATE_KEY id_rsa
 
 deploy:
   tags:
@@ -45,5 +49,7 @@ execute:
   tags:
     - terraform
     - docker
+  rules:
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
   script:
     - echo Greetings from runner!
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000000000000000000000000000000000000..bbb7eb032b0a36b9863bca4e6c2f1a29fe7f8fbc
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,3 @@
+# 2023-02-13
+
+- !1 Unregister gitlab runner on VM destruction
diff --git a/README.md b/README.md
index 4c42cd06d27bd95f75d2550292e6c640409aef89..aa3d0325d1c677b89f366a1dda5335f439f956fe 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,22 @@ project), projects using Terraform need:
   a variable `REGISTRATION_TOKEN` that points to another project
   dedicated for the build itself.)
 
+- This project needs a pair of passphrase-less SSH private/public keys
+  for the GitLab shared runner to be able to connect to the deployed
+  runners to unregister them from GitLab before deletion.
+  You can use the following command to create a pair of SSH private/public keys
+  without passphrase in the current directory (files `id_rsa` and `id_rsa.pub`):
+  `ssh-keygen -b 4096 -f id_rsa -N ""`.
+  - The contents of the private key file `id_rsa` should be added as a variable
+    `SSH_PRIVATE_KEY` of type **File**  in CI/CD settings.
+    See the
+    [intro](https://gitlab.inria.fr/gitlabci_gallery/intro#cicd-variables)
+    project for details on how to set a CI/CD variable.
+  - The public key file `id_rsa.pub` should be registered on ci.inria.fr portal
+    to allow the dedicated user to connect to the hosted virtual machines
+    ([portal documentation](https://ci.inria.fr/doc/page/slaves_access_tutorial/#register-your-ssh-public-key)).
+    for details on how to register a public key on the portal.
+
 - The repository contains a `backend.tf` file for connecting
   Terraform with GitLab.
   This may be convenient to delete it locally for running `terraform`
@@ -140,6 +156,17 @@ variable "REGISTRATION_TOKEN" {
 The variable is marked as sensitive to prevent Terraform from showing its value in output
 ([Terraform documentation](https://developer.hashicorp.com/terraform/language/values/variables#suppressing-values-in-cli-output)). 
 
+The SSH public key is passed through the variable `SSH_PUBLIC_KEY`.
+```terraform
+variable "SSH_PUBLIC_KEY" {
+  type = string
+}
+```
+The value of the `SSH_PUBLIC_KEY` variable will be stored in the file
+`~/.ssh/authorized_keys` in virtual machines, so that Terraform can
+connect to the virtual machines with the private key to unregister the
+runners before destroying the machines.
+
 In this example, we set up only one resource:
 ```terraform
 resource "cloudstack_instance" "custom_instance" {
@@ -147,7 +174,7 @@ resource "cloudstack_instance" "custom_instance" {
   ## in VM names.
   name             = "gitlabcigallery-terraform-custom-instance"
   service_offering = "Custom"
-  template         = "ubuntu-20.04-cloudinit"
+  template         = "ubuntu-20.04-lts"
   zone             = "zone-ci"
   details = {
     cpuNumber = 4
@@ -156,7 +183,21 @@ resource "cloudstack_instance" "custom_instance" {
   expunge   = true
   user_data = templatefile("cloud-init.sh.tftpl", {
     REGISTRATION_TOKEN = var.REGISTRATION_TOKEN
+    SSH_PUBLIC_KEY     = var.SSH_PUBLIC_KEY
   })
+  connection {
+    type                = "ssh"
+    host                = self.name
+    user                = "ci"
+    private_key         = file("id_rsa")
+    bastion_host        = "ci-ssh.inria.fr"
+    bastion_user        = "gter001"
+    bastion_private_key = file("id_rsa")
+  }
+  provisioner "remote-exec" {
+    when   = destroy
+    inline = ["sudo gitlab-runner unregister --all-runners || true"]
+  }
 }
 ```
 
@@ -189,9 +230,24 @@ resource "cloudstack_instance" "custom_instance" {
   [`cloud-init.sh.tftpl`](cloud-init.sh.tftpl)
   by substituting `${REGISTRATION_TOKEN}` with the value of the variable
   passed to Terraform.
-
-The script [`cloud-init.sh.tftpl`](cloud-init.sh.tftpl) installs
-`gitlab-runner` and `docker.io` on the virtual machine and registers the runner.
+- We pass also the `SSH_PUBLIC_KEY` to the template file to have its
+  value written in the `~/.ssh/authorized_keys` file.
+  We configure the connection via ssh to the runner: `gter001` is the
+  login of the dedicated user on ci.inria.fr, and we will make sure
+  in the next section that the private key is written in the file
+  `id_rsa`.
+  We cannot use a variable for passing the path to this file,
+  since the connection is used by a destroy provisioner, that
+  cannot refer to variables.
+  This destroy provisioner executes `gitlab-runner unregister`
+  before the destruction of the virtual machine; failures are ignored
+  in case of the `gitlab-runner` command was not yet installed
+  when destroying occurs.
+
+The script [`cloud-init.sh.tftpl`](cloud-init.sh.tftpl) registers
+the SSH public key in `~ci/.ssh/authorized_keys`,
+installs `gitlab-runner` and `docker.io` on the virtual machine,
+and registers the runner.
 
 ## The pipeline specification file [`.gitlab-ci.yml`](.gitlab-ci.yml)
 
@@ -239,6 +295,21 @@ stages:
   the same project, but we could have chosen to register the runner to
   another project by adjusting the `REGISTRATION_TOKEN` variable.)
 
+Every job that will use the Terraform configuration file needs to copy
+the file referred by `SSH_PRIVATE_KEY` into the file `id_rsa`.
+To copy the file without overriding all the script,
+we use the `before_script` key. For instance, in the job `validate`:
+
+```yaml
+validate:
+  tags:
+    - linux
+    - small
+  extends: .terraform:validate
+  before_script:
+    - cp $SSH_PRIVATE_KEY id_rsa
+```
+
 ## Ignored files in [`.gitignore`](.gitignore)
 
 [`.gitignore`](.gitignore) instructs git to ignore the local files
diff --git a/cloud-init.sh.tftpl b/cloud-init.sh.tftpl
index daab7bbf577b9be9fe158bc5ce9880a24baa4048..23949122194d8246d3f541f2043c0ed176fede8c 100644
--- a/cloud-init.sh.tftpl
+++ b/cloud-init.sh.tftpl
@@ -1,15 +1,20 @@
-#!/bin/sh
+#!/bin/bash
 # Standard output and errors are redirected to /root/log.txt to ease
 # debugging.
->>/root/log.txt 2>&1 (
+(
+  # To be able to run `sudo gitlab-runner unregister --all-runners` on
+  # VM destruction.
+  echo 'ci ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/90-ci
+  mkdir -p -m 700 ~ci/.ssh
+  echo ${SSH_PUBLIC_KEY} >>~ci/.ssh/authorized_keys
   # GitLab needs a recent version of `gitlab-runner` to be compatible with
   # the instance running on gitlab.inria.fr. The version packaged by default
   # on Ubuntu is regularly out of date.
   curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
-  apt-get update
+  # apt-get update performed by the script above
   # We install docker.io to be able to register a docker executor
   apt-get install --yes gitlab-runner docker.io
   gitlab-runner register --non-interactive --tag-list terraform,docker \
     --executor docker --docker-image alpine --url https://gitlab.inria.fr \
     --registration-token ${REGISTRATION_TOKEN}
-)
+) >>/root/log.txt 2>&1
diff --git a/main.tf b/main.tf
index 950a1b04accd0a5a4868b634ac782ca1ed3ec389..dca84281a5193fdad2c9f5115c55a13d2a1d7da1 100644
--- a/main.tf
+++ b/main.tf
@@ -20,12 +20,16 @@ variable "REGISTRATION_TOKEN" {
   sensitive = true
 }
 
+variable "SSH_PUBLIC_KEY" {
+  type = string
+}
+
 resource "cloudstack_instance" "custom_instance" {
   ## It is a good practice to have the "{project name}-" prefix
   ## in VM names.
   name             = "gitlabcigallery-terraform-custom-instance"
   service_offering = "Custom"
-  template         = "ubuntu-20.04-cloudinit"
+  template         = "ubuntu-20.04-lts"
   zone             = "zone-ci"
   details = {
     cpuNumber = 4
@@ -34,5 +38,19 @@ resource "cloudstack_instance" "custom_instance" {
   expunge = true
   user_data = templatefile("cloud-init.sh.tftpl", {
     REGISTRATION_TOKEN = var.REGISTRATION_TOKEN
+    SSH_PUBLIC_KEY     = var.SSH_PUBLIC_KEY
   })
+  connection {
+    type                = "ssh"
+    host                = self.name
+    user                = "ci"
+    private_key         = file("id_rsa")
+    bastion_host        = "ci-ssh.inria.fr"
+    bastion_user        = "gter001"
+    bastion_private_key = file("id_rsa")
+  }
+  provisioner "remote-exec" {
+    when   = destroy
+    inline = ["sudo gitlab-runner unregister --all-runners || true"]
+  }
 }