diff --git a/ci/requirements.yml b/ci/requirements.yml
index ab5c152319a46be33b9a5ce4c02df09e7dbc2109..6467dd539bebeae2f1717fe273d8bf1e0164e489 100644
--- a/ci/requirements.yml
+++ b/ci/requirements.yml
@@ -2,4 +2,7 @@
 collections:
 - ansible.posix
 - ansible.utils
-- community.general
+- name: community.general
+  version: 4.8.8
+  # 5.0.0 dropped compatibility with ansible 2.9 and ansible-base 2.10
+  # Ref.: https://github.com/ansible-collections/community.general/commit/1a9b3214fdf1eaccba5cc9ee210cbc5b5070fe4b
diff --git a/ci/roles/volume/tasks/main.yml b/ci/roles/volume/tasks/main.yml
index 62bb76ad523e470221836225520bd49d39d3cd08..de0686a1be494561d3dd1699ccbf6218d329d4fe 100644
--- a/ci/roles/volume/tasks/main.yml
+++ b/ci/roles/volume/tasks/main.yml
@@ -97,60 +97,70 @@
     - ansible_volume1
     - ansible_volume2
 
-- name: Create a test image file
-  shell: mktemp
-  register: tmp_file
-
-- name: Fill test image file to 1MB
-  shell: truncate -s 1048576 {{ tmp_file.stdout }}
-
-- name: Create test image
-  openstack.cloud.image:
-     cloud: "{{ cloud }}"
-     state: present
-     name: "{{ test_volume_image }}"
-     filename: "{{ tmp_file.stdout }}"
-     disk_format: raw
-     tags: "{{ image_tags }}"
-  register: returned_image
-
-- name: Create volume from image
-  openstack.cloud.volume:
-     cloud: "{{ cloud }}"
-     state: present
-     size: 1
-     image: "{{ test_volume_image }}"
-     name: ansible_volume2
-     description: Test volume
-  register: vol
-
-- name: Delete volume from image
-  openstack.cloud.volume:
-     cloud: "{{ cloud }}"
-     name: ansible_volume2
-     state: absent
-  register: vol
-
-- name: Create test shared image
-  openstack.cloud.image:
-     cloud: "{{ cloud }}"
-     state: present
-     name: "{{ image_name }}"
-     filename: "{{ tmp_file.stdout }}"
-     is_public: true
-     disk_format: raw
-     tags: "{{ image_tags }}"
-  register: returned_image
-
-- name: Delete test shared image
-  openstack.cloud.image:
-     cloud: "{{ cloud }}"
-     state: absent
-     name: "{{ image_name }}"
-     filename: "{{ tmp_file.stdout }}"
-     is_public: true
-     disk_format: raw
-     tags: "{{ image_tags }}"
-  register: returned_image
+- name: Test images
+  block:
+    - name: Ensure clean environment
+      ansible.builtin.set_fact:
+        tmp_file: !!null
+
+    - name: Create a test image file
+      ansible.builtin.tempfile:
+      register: tmp_file
+
+    - name: Fill test image file to 1MB
+      community.general.filesize:
+        path: '{{ tmp_file.path }}'
+        size: 1M
+
+    - name: Create test image
+      openstack.cloud.image:
+         cloud: "{{ cloud }}"
+         state: present
+         name: "{{ test_volume_image }}"
+         filename: "{{ tmp_file.path }}"
+         disk_format: raw
+         tags: "{{ image_tags }}"
+
+    - name: Create volume from image
+      openstack.cloud.volume:
+         cloud: "{{ cloud }}"
+         state: present
+         size: 1
+         image: "{{ test_volume_image }}"
+         name: ansible_volume2
+         description: Test volume
+
+    - name: Delete volume from image
+      openstack.cloud.volume:
+         cloud: "{{ cloud }}"
+         name: ansible_volume2
+         state: absent
+
+    - name: Create test shared image
+      openstack.cloud.image:
+         cloud: "{{ cloud }}"
+         state: present
+         name: "{{ image_name }}"
+         filename: "{{ tmp_file.path }}"
+         is_public: true
+         disk_format: raw
+         tags: "{{ image_tags }}"
+
+    - name: Delete test shared image
+      openstack.cloud.image:
+         cloud: "{{ cloud }}"
+         state: absent
+         name: "{{ image_name }}"
+         filename: "{{ tmp_file.path }}"
+         is_public: true
+         disk_format: raw
+         tags: "{{ image_tags }}"
+
+  always:
+    - name: Remove temporary image file
+      ansible.builtin.file:
+        path: "{{ tmp_file.path }}"
+        state: absent
+      when: tmp_file is defined and 'path' in tmp_file
 
 - include_tasks: volume_info.yml