diff --git a/Ansible/roles/debian_server/files/dvb b/Ansible/roles/debian_server/files/dvb
new file mode 100755
index 0000000000000000000000000000000000000000..00d0ed59b3b7e4802c93371bcb46a7c304fc0c27
--- /dev/null
+++ b/Ansible/roles/debian_server/files/dvb
@@ -0,0 +1,77 @@
+#!/bin/sh
+set -eu
+
+# Cleanup of snapshots, added as trap so it's always executed even in case of a
+# failure or ctrl+c.
+cleanup () {
+    btrfs subvolume delete "$snapshot"
+}
+
+usage () {
+    echo "Usage: $(basename "$0") DESTINATION" >&2
+    exit 1
+}
+
+get_mountpoint () {
+    df --output=target "$1" | tail +2
+}
+
+get_snapshot () {
+    name="$1"
+    mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name" )"
+    snapshot="$(dirname "$mountpoint")/snapshot)"
+    echo "$snapshot"
+}
+
+if [ "$#" -ne 1 ]
+then
+    usage
+elif [ ! -d "$1" ]
+then
+    echo "Destinaton $1 not found or is not a directory." >&2
+    exit 1
+elif [ "$(id -u)" -ne 0 ]
+then
+    echo "This program must run as root." >&2
+    exit 1
+elif ! command -v btrfs >/dev/null
+then
+    echo 'btrfs-progs must be installed.' >&2
+    exit 1
+elif ! docker info --format '{{ .ServerVersion }}' >/dev/null
+then
+    echo 'Docker must be installed and running.' >&2
+    exit 1
+elif ! df --type=btrfs /var/lib/docker/volumes >/dev/null
+then
+    echo "Docker volumes are not on a btrfs filesystem." >&2
+    exit 1
+elif ! df --type=btrfs "$1" >/dev/null
+then
+    echo "Destination $1 is not on a btrfs filesystem." >&2
+    exit 1
+elif [ "$(df --output=target /var/lib/docker/volumes)" != "$(df --output=target "$1")" ]
+then
+    echo "The destination $1 is not on the same btrfs volume as the Docker volumes." >&2
+    exit 1
+elif [ -z "$(docker volume ls --filter label=snapshot=true --format '{{ .Name }}')" ]
+then
+    echo "No Docker volumes marked for backup, exiting." >&2
+    exit 1
+fi
+
+dest="$1"
+root_volume="$(get_mountpoint /var/lib/docker/volumes)"
+snapshot="$(mktemp --dry-run "--tmpdir=$root_volume")"
+trap 'cleanup' INT QUIT EXIT TERM
+btrfs subvolume snapshot "$root_volume" "$snapshot"
+
+for name in $(docker volume ls --filter label=snapshot=true --format '{{ .Name }}')
+do
+    echo "Backing up $name."
+    mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name")"
+    src="$snapshot/${mountpoint#$root_volume/}"
+    # shellcheck disable=SC2115
+    [ ! -e "$dest/$name" ] || rm -rf "$dest/$name"
+    cp --archive --force --reflink=always "$src" "$dest/$name"
+done
diff --git a/Ansible/roles/debian_server/files/dvb.service b/Ansible/roles/debian_server/files/dvb.service
new file mode 100644
index 0000000000000000000000000000000000000000..37617a3ea35fc2d5d5708cda417ab4c209799aa9
--- /dev/null
+++ b/Ansible/roles/debian_server/files/dvb.service
@@ -0,0 +1,9 @@
+# vim: filetype=systemd
+[Unit]
+Description=Docker volume backup (dvb)
+ConditionACPower=true
+After=local-fs.target
+
+[Service]
+Type=exec
+ExecStart=dvb /var/backups/docker-volumes
diff --git a/Ansible/roles/debian_server/files/dvb.timer b/Ansible/roles/debian_server/files/dvb.timer
new file mode 100644
index 0000000000000000000000000000000000000000..5606bdd5a645d6c15b3254b560d9b75a60ec11ed
--- /dev/null
+++ b/Ansible/roles/debian_server/files/dvb.timer
@@ -0,0 +1,10 @@
+# vim: filetype=systemd
+[Unit]
+Description=Docker volume backup (dvb)
+
+[Timer]
+OnCalendar=weekly
+RandomizedDelaySec=21600
+
+[Install]
+WantedBy=multi-user.target
diff --git a/Ansible/roles/debian_server/tasks/btrfs.yml b/Ansible/roles/debian_server/tasks/btrfs.yml
index 2b782eed5c185080af2d62103471bdfad65ed37c..709106985155e8f0ad2f90f6229238fcbf1e4700 100644
--- a/Ansible/roles/debian_server/tasks/btrfs.yml
+++ b/Ansible/roles/debian_server/tasks/btrfs.yml
@@ -30,6 +30,8 @@
       {{ (ansible_facts.mounts|selectattr("device", "equalto", device)|first)["mount"] }}
     # yamllint enable rule:line-length
   tags: [scrub]
+  notify:
+    - Systemd daemon reload
 
 - name: Enable btrfs scrub timers
   ansible.builtin.systemd:
@@ -46,6 +48,8 @@
     mode: 0o0644
     src: btrfs_check.{{ item }}.j2
   vars: *vars
+  notify:
+    - Systemd daemon reload
 
 - name: Enable the btrfs check timers
   ansible.builtin.systemd:
diff --git a/Ansible/roles/debian_server/tasks/docker.yml b/Ansible/roles/debian_server/tasks/docker.yml
index ca4a3817fb759787e8467f38000633e14b66f26b..9c15907411612f89f5967bb7c9d1c80ce23e6387 100644
--- a/Ansible/roles/debian_server/tasks/docker.yml
+++ b/Ansible/roles/debian_server/tasks/docker.yml
@@ -37,6 +37,7 @@
     mode: 0o0644
   notify:
     - Systemd daemon reload
+    - Restart Docker
 
 - name: Allow access from the toolbox container
   ansible.builtin.copy:
@@ -54,3 +55,35 @@
     direction: in
     interface: docker0
     rule: allow
+
+- name: Create the Docker volume backup destination directory
+  ansible.builtin.file:
+    group: backup
+    mode: 0o0750
+    owner: root
+    path: /var/backups/docker-volumes
+    state: directory
+
+- name: Copy the Docker volume backup script
+  ansible.builtin.copy:
+    dest: /usr/local/bin/
+    mode: preserve
+    src: dvb
+
+- name: Copy the Docker volume backup service and timer
+  loop:
+    - service
+    - timer
+  ansible.builtin.copy:
+    dest: /etc/systemd/system/dvb.{{ item }}
+    mode: 0o0644
+    src: dvb.{{ item }}
+  notify:
+    - Systemd daemon reload
+
+- name: Enable the Docker volume backup timer
+  ansible.builtin.systemd:
+    enabled: true
+    name: dvb.timer
+  notify:
+    - Systemd daemon reload
diff --git a/Ansible/roles/debian_server/tasks/main.yml b/Ansible/roles/debian_server/tasks/main.yml
index 665a8162a013b5508469878699f104467671433c..447d9e663ef10969bab2bdbdb476c2fe75c00837 100644
--- a/Ansible/roles/debian_server/tasks/main.yml
+++ b/Ansible/roles/debian_server/tasks/main.yml
@@ -100,12 +100,18 @@
 - name: Include Docker tasks
   ansible.builtin.include_tasks:
     file: docker.yml
+    apply:
+      tags:
+        - docker
   tags:
     - always
 
 - name: Include web tasks
   ansible.builtin.include_tasks:
     file: web.yml
+    apply:
+      tags:
+        - web
   tags:
     - always