diff --git a/ansible/group_vars/all/docker b/ansible/group_vars/all/docker
new file mode 100644
index 000000000..508902319
--- /dev/null
+++ b/ansible/group_vars/all/docker
@@ -0,0 +1,19 @@
+---
+###############################################################################
+# Docker configuration.
+
+# Name of the docker storage LVM volume group.
+docker_storage_volume_group: data
+
+# Name of the docker storage data LVM volume.
+docker_storage_volume_thinpool: docker-thinpool
+
+# Size of the docker storage data LVM volume (see lvol module size argument).
+docker_storage_volume_thinpool_size: 20%VG
+
+# Name of the docker storage metadata LVM volume.
+docker_storage_volume_thinpool_meta: docker-thinpoolmeta
+
+# Size of the docker storage metadata LVM volume (see lvol module size
+# argument).
+docker_storage_volume_thinpool_meta_size: 1%VG
diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml
new file mode 100644
index 000000000..59c3bdaf9
--- /dev/null
+++ b/ansible/roles/docker/defaults/main.yml
@@ -0,0 +1,22 @@
+---
+# Name of the docker storage LVM volume group.
+docker_storage_volume_group:
+
+# Name of the docker storage data LVM volume.
+docker_storage_volume_thinpool:
+
+# Size of the docker storage data LVM volume (see lvol module size argument).
+docker_storage_volume_thinpool_size:
+
+# Name of the docker storage metadata LVM volume.
+docker_storage_volume_thinpool_meta:
+
+# Size of the docker storage metadata LVM volume (see lvol module size
+# argument).
+docker_storage_volume_thinpool_meta_size:
+
+# Threshold at which to extend thin-provisioned docker storage volumes.
+docker_storage_thinpool_autoextend_threshold: 80
+
+# Percentage by which to extend thin-provisioned docker storage volumes.
+docker_storage_thinpool_autoextend_percent: 20
diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml
index 4e065b724..a0b7bf66e 100644
--- a/ansible/roles/docker/tasks/main.yml
+++ b/ansible/roles/docker/tasks/main.yml
@@ -5,3 +5,22 @@
     groups: docker
     append: yes
   become: True
+
+- name: Check whether docker storage is in loopback mode
+  command: docker info
+  register: docker_info
+  changed_when: False
+  become: True
+
+- name: Fail when loopback-mode containers or images exist
+  fail:
+    msg: >
+      Not configuring docker storage in direct-lvm mode as loopback-backed
+      containers or images exist.
+  when:
+    - "{{ 'Data loop file' in docker_info.stdout }}"
+    - "{{ 'Images: 0' not in docker_info.stdout }}"
+    - "{{ 'Containers: 0' not in docker_info.stdout }}"
+
+- include: storage.yml
+  when: "{{ 'Data loop file' in docker_info.stdout }}"
diff --git a/ansible/roles/docker/tasks/storage.yml b/ansible/roles/docker/tasks/storage.yml
new file mode 100644
index 000000000..09fda5fc4
--- /dev/null
+++ b/ansible/roles/docker/tasks/storage.yml
@@ -0,0 +1,61 @@
+---
+- name: Ensure the docker daemon is stopped
+  service:
+    name: docker
+    state: stopped
+  become: True
+
+- name: Ensure loopback storage state is absent
+  file:
+    path: "{{ item }}"
+    state: absent
+  with_items:
+    - "/var/lib/docker/devicemapper"
+    - "/var/lib/docker/images"
+    - "/var/lib/docker/containers"
+  become: True
+
+- name: Ensure the docker storage data and metadata volumes exist
+  lvol:
+    vg: "{{ docker_storage_volume_group }}"
+    lv: "{{ item.name }}"
+    size: "{{ item.size }}"
+    shrink: no
+    state: present
+  with_items:
+    - name: "{{ docker_storage_volume_thinpool }}"
+      size: "{{ docker_storage_volume_thinpool_size }}"
+    - name: "{{ docker_storage_volume_thinpool_meta }}"
+      size: "{{ docker_storage_volume_thinpool_meta_size }}"
+  become: True
+
+- name: Ensure the docker storage volume is converted to a thinpool
+  command: >
+    lvconvert -y --zero n -c 512K 
+    --thinpool {{ docker_storage_volume_group }}/{{ docker_storage_volume_thinpool }}
+    --poolmetadata {{ docker_storage_volume_group }}/{{ docker_storage_volume_thinpool_meta }}
+  become: True
+
+- name: Ensure the docker storage metadata profile exists
+  template:
+    src: docker-thinpool.profile.j2
+    dest: /etc/lvm/profile/docker-thinpool.profile
+  become: True
+
+- name: Ensure the docker storage metadata profile is applied
+  command: >
+    lvchange --metadataprofile docker-thinpool
+    {{ docker_storage_volume_group }}/{{ docker_storage_volume_thinpool }}
+  become: True
+
+- name: Ensure the docker daemon configuration file exists
+  template:
+    src: daemon.json.j2
+    dest: /etc/docker/daemon.json
+  become: True
+
+- name: Ensure the docker daemon is running
+  service:
+    name: docker
+    state: started
+  become: True
diff --git a/ansible/roles/docker/templates/daemon.json.j2 b/ansible/roles/docker/templates/daemon.json.j2
new file mode 100644
index 000000000..a02fc1a79
--- /dev/null
+++ b/ansible/roles/docker/templates/daemon.json.j2
@@ -0,0 +1,8 @@
+{
+  "storage-driver": "devicemapper",
+   "storage-opts": [
+     "dm.thinpooldev=/dev/mapper/{{ docker_storage_volume_group | replace('-', '--') }}-{{ docker_storage_volume_thinpool | replace('-', '--') }}",
+     "dm.use_deferred_removal=true",
+     "dm.use_deferred_deletion=true"
+   ]
+}
diff --git a/ansible/roles/docker/templates/docker-thinpool.profile.j2 b/ansible/roles/docker/templates/docker-thinpool.profile.j2
new file mode 100644
index 000000000..4627e2ba2
--- /dev/null
+++ b/ansible/roles/docker/templates/docker-thinpool.profile.j2
@@ -0,0 +1,4 @@
+activation {
+  thin_pool_autoextend_threshold={{ docker_storage_thinpool_autoextend_threshold }}
+  thin_pool_autoextend_percent={{ docker_storage_thinpool_autoextend_percent }}
+}
diff --git a/etc/kayobe/docker.yml b/etc/kayobe/docker.yml
new file mode 100644
index 000000000..6186b2d7d
--- /dev/null
+++ b/etc/kayobe/docker.yml
@@ -0,0 +1,23 @@
+---
+###############################################################################
+# Docker configuration.
+
+# Name of the docker storage LVM volume group.
+#docker_storage_volume_group:
+
+# Name of the docker storage data LVM volume.
+#docker_storage_volume_thinpool:
+
+# Size of the docker storage data LVM volume (see lvol module size argument).
+#docker_storage_volume_thinpool_size:
+
+# Name of the docker storage metadata LVM volume.
+#docker_storage_volume_thinpool_meta:
+
+# Size of the docker storage metadata LVM volume (see lvol module size
+# argument).
+#docker_storage_volume_thinpool_meta_size:
+
+###############################################################################
+# Dummy variable to allow Ansible to accept this file.
+workaround_ansible_issue_8743: yes