diff --git a/.gitlab-ci/terraform.yml b/.gitlab-ci/terraform.yml
index dfe15e9059d62728d01ca5dbb7911a1ddbbac105..fa891beb878f4fa5ec75dddf6e11affff3611935 100644
--- a/.gitlab-ci/terraform.yml
+++ b/.gitlab-ci/terraform.yml
@@ -104,6 +104,16 @@ tf-validate-aws:
   OS_INTERFACE: public
   OS_IDENTITY_API_VERSION: "3"
 
+tf-ovh_cleanup:
+  stage: unit-tests
+  image: python
+  variables:
+    <<: *ovh_variables
+  before_script:
+    - pip install -r scripts/openstack-cleanup/requirements.txt
+  script:
+    - ./scripts/openstack-cleanup/main.py
+
 tf-ovh_ubuntu18-calico:
   extends: .terraform_apply
   when: on_success
diff --git a/scripts/openstack-cleanup/.gitignore b/scripts/openstack-cleanup/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..61f5948973144482b6b2885f1fbe5480f955c5bb
--- /dev/null
+++ b/scripts/openstack-cleanup/.gitignore
@@ -0,0 +1 @@
+openrc
diff --git a/scripts/openstack-cleanup/main.py b/scripts/openstack-cleanup/main.py
new file mode 100755
index 0000000000000000000000000000000000000000..00c2ad5ed2f881e3219e408d308266438499f3e3
--- /dev/null
+++ b/scripts/openstack-cleanup/main.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+import argparse
+import openstack
+import logging
+import datetime
+import time
+from pprint import pprint
+
+DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
+PAUSE_SECONDS = 5
+
+log = logging.getLogger('openstack-cleanup')
+
+parser = argparse.ArgumentParser(description='Cleanup OpenStack VMs')
+
+parser.add_argument('-v', '--verbose', action='store_true',
+                    help='Increase verbosity')
+parser.add_argument('--hours', type=int, default=4,
+                    help='Age (in hours) of VMs to cleanup')
+parser.add_argument('--dry-run', action='store_true',
+                    help='Do not delete anything')
+
+args = parser.parse_args()
+
+oldest_allowed = datetime.datetime.now() - datetime.timedelta(hours=args.hours)
+
+
+def main():
+    if args.dry_run:
+        print('Running in dry-run mode')
+    else:
+        print('This will delete VMs... (ctrl+c to cancel)')
+        time.sleep(PAUSE_SECONDS)
+
+    conn = openstack.connect()
+    for server in conn.compute.servers():
+        created_at = datetime.datetime.strptime(server.created_at, DATE_FORMAT)
+        if created_at < oldest_allowed:
+            print('Will delete server %(name)s' % server)
+            if not args.dry_run:
+                conn.compute.delete_server(server)
+
+
+if __name__ == '__main__':
+    # execute only if run as a script
+    main()
diff --git a/scripts/openstack-cleanup/requirements.txt b/scripts/openstack-cleanup/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..81c57a64f01f4ff7479d3b6b9d3c5b023fcd881e
--- /dev/null
+++ b/scripts/openstack-cleanup/requirements.txt
@@ -0,0 +1 @@
+openstacksdk>=0.43.0