diff --git a/playbooks/roles/docker/README.md b/playbooks/roles/docker/README.md
new file mode 100644
index 0000000..2e8a47f
--- /dev/null
+++ b/playbooks/roles/docker/README.md
@@ -0,0 +1,27 @@
+# Role `docker`
+
+Makes sure Docker Engine and other related packages are installed from the Docker repos on the specified hosts.
+For details see: [`tasks/main/02_docker_install.yaml`](./tasks/main/02_docker_install.yaml).
+
+## Supported Distributions
+
+The following distributions are supported:
+
+- Debian 11
+
+## Required Arguments
+
+None.
+
+## Updates
+
+This role doesn't handle updates.
+However it uses the system package manager for installing Docker Engine and the other related packages, so when you're making sure the system packages are up-to-date, you're handling updates for the packages installed by this role as well.
+
+## `hosts`
+
+The `hosts` for this role need to be the machines for which you want to make sure Docker Engine and other related packages are installed from the Docker repos.
+
+## Links & Resources
+
+-
diff --git a/playbooks/roles/docker/meta/main.yaml b/playbooks/roles/docker/meta/main.yaml
new file mode 100644
index 0000000..633049c
--- /dev/null
+++ b/playbooks/roles/docker/meta/main.yaml
@@ -0,0 +1,8 @@
+---
+dependencies:
+ - role: distribution_check
+ vars:
+ distribution_check__distribution_support_spec:
+ - name: Debian
+ versions:
+ - 11
diff --git a/playbooks/roles/docker/tasks/main.yaml b/playbooks/roles/docker/tasks/main.yaml
new file mode 100644
index 0000000..7f5874d
--- /dev/null
+++ b/playbooks/roles/docker/tasks/main.yaml
@@ -0,0 +1,7 @@
+- name: make sure the Docker repo is setup
+ ansible.builtin.import_tasks:
+ file: main/01_repo_setup.yaml
+
+- name: make sure Docker Engine and other related packages are installed
+ ansible.builtin.import_tasks:
+ file: main/02_docker_install.yaml
diff --git a/playbooks/roles/docker/tasks/main/01_repo_setup.yaml b/playbooks/roles/docker/tasks/main/01_repo_setup.yaml
new file mode 100644
index 0000000..5134542
--- /dev/null
+++ b/playbooks/roles/docker/tasks/main/01_repo_setup.yaml
@@ -0,0 +1,15 @@
+- name: make sure Dockers GPG key is added
+ ansible.builtin.get_url:
+ url: https://download.docker.com/linux/debian/gpg
+ dest: /etc/apt/trusted.gpg.d/docker.asc
+ mode: "0644"
+ owner: root
+ group: root
+ become: true
+
+- name: make sure Dockers APT repository is added
+ ansible.builtin.apt_repository:
+ repo: deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/docker.asc] https://download.docker.com/linux/debian bullseye stable
+ filename: docker
+ state: present
+ become: true
diff --git a/playbooks/roles/docker/tasks/main/02_docker_install.yaml b/playbooks/roles/docker/tasks/main/02_docker_install.yaml
new file mode 100644
index 0000000..5617195
--- /dev/null
+++ b/playbooks/roles/docker/tasks/main/02_docker_install.yaml
@@ -0,0 +1,11 @@
+- name: make sure Docker Engine and other related packages are installed
+ ansible.builtin.apt:
+ name:
+ - docker-ce
+ - docker-ce-cli
+ - containerd.io
+ - docker-buildx-plugin
+ - docker-compose-plugin
+ state: present
+ update_cache: true
+ become: true
diff --git a/playbooks/roles/docker_compose/README.md b/playbooks/roles/docker_compose/README.md
new file mode 100644
index 0000000..d407a62
--- /dev/null
+++ b/playbooks/roles/docker_compose/README.md
@@ -0,0 +1,34 @@
+# Role `docker_compose`
+
+A role for deploying a Docker-Compose-based application.
+It deploys the given Compose file as well as configuration files to the specified hosts and makes sure all services are up-to-date and running.
+The Compose file gets deployed to `/ansible_docker_compose/compose.yaml` and the configuration files get deployed into the `/ansible_docker_compose/configs/` directory.
+A use case for the deployment of the additional configuration files is Composes top-level element `configs` in conjunction with the `configs` option for services.
+
+## Supported Distributions
+
+The following distributions are supported:
+
+- Debian 11
+
+## Required Arguments
+
+For the required arguments look at the [`argument_specs.yaml`](./meta/argument_specs.yaml).
+
+## `hosts`
+
+The `hosts` for this role need to be the machines, for which you want to make sure the given Compose file is deployed and all services of it are up-to-date and running.
+
+## Links & Resources
+
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
diff --git a/playbooks/roles/docker_compose/defaults/main.yaml b/playbooks/roles/docker_compose/defaults/main.yaml
new file mode 100644
index 0000000..76831d6
--- /dev/null
+++ b/playbooks/roles/docker_compose/defaults/main.yaml
@@ -0,0 +1 @@
+docker_compose__configuration_files: [ ]
diff --git a/playbooks/roles/docker_compose/handlers/main.yaml b/playbooks/roles/docker_compose/handlers/main.yaml
new file mode 100644
index 0000000..96c5ab3
--- /dev/null
+++ b/playbooks/roles/docker_compose/handlers/main.yaml
@@ -0,0 +1,6 @@
+- name: docker compose down
+ ansible.builtin.command:
+ cmd: /usr/bin/docker compose down
+ chdir: /ansible_docker_compose
+ become: true
+ changed_when: true # This is always changed.
diff --git a/playbooks/roles/docker_compose/meta/argument_specs.yaml b/playbooks/roles/docker_compose/meta/argument_specs.yaml
new file mode 100644
index 0000000..81ce504
--- /dev/null
+++ b/playbooks/roles/docker_compose/meta/argument_specs.yaml
@@ -0,0 +1,26 @@
+argument_specs:
+ main:
+ options:
+ docker_compose__compose_file_content:
+ description: >-
+ The content of the Compose file at
+ `/ansible_docker_compose/compose.yaml`.
+ type: str
+ required: true
+ docker_compose__configuration_files:
+ description: >-
+ A list of configuration files to be deployed in the
+ `/ansible_docker_compose/configs/` directory.
+ type: list
+ elements: dict
+ required: false
+ default: [ ]
+ options:
+ name:
+ description: The name of the configuration file.
+ type: str
+ required: true
+ content:
+ description: The content of the configuration file.
+ type: str
+ required: true
diff --git a/playbooks/roles/docker_compose/meta/main.yaml b/playbooks/roles/docker_compose/meta/main.yaml
new file mode 100644
index 0000000..8bd04af
--- /dev/null
+++ b/playbooks/roles/docker_compose/meta/main.yaml
@@ -0,0 +1,9 @@
+---
+dependencies:
+ - role: distribution_check
+ vars:
+ distribution_check__distribution_support_spec:
+ - name: Debian
+ versions:
+ - 11
+ - role: docker
diff --git a/playbooks/roles/docker_compose/tasks/main.yaml b/playbooks/roles/docker_compose/tasks/main.yaml
new file mode 100644
index 0000000..dafe558
--- /dev/null
+++ b/playbooks/roles/docker_compose/tasks/main.yaml
@@ -0,0 +1,97 @@
+- name: make sure the `/ansible_docker_compose` directory exists
+ ansible.builtin.file:
+ path: /ansible_docker_compose
+ state: directory
+ mode: "0755"
+ owner: root
+ group: root
+ become: true
+
+- name: deploy the Compose file
+ ansible.builtin.copy:
+ content: "{{ docker_compose__compose_file_content }}"
+ dest: /ansible_docker_compose/compose.yaml
+ mode: "0644"
+ owner: root
+ group: root
+ become: true
+ notify: docker compose down
+
+- name: make sure the `/ansible_docker_compose/configs` directory exists
+ ansible.builtin.file:
+ path: /ansible_docker_compose/configs
+ state: directory
+ mode: "0755"
+ owner: root
+ group: root
+ become: true
+
+- name: set `docker_compose__config_files_to_exist` fact initially to an empty list
+ ansible.builtin.set_fact:
+ docker_compose__config_files_to_exist: [ ]
+
+- name: add names from `docker_compose__configuration_files` to `docker_compose__config_files_to_exist` fact
+ ansible.builtin.set_fact:
+ docker_compose__config_files_to_exist: "{{ docker_compose__config_files_to_exist + [ item.name ] }}" # noqa: jinja[spacing]
+ loop: "{{ docker_compose__configuration_files }}"
+
+- name: find configuration files to remove
+ ansible.builtin.find:
+ paths: /ansible_docker_compose/configs/
+ recurse: false
+ excludes: "{{ docker_compose__config_files_to_exist }}"
+ register: docker_compose__config_files_to_remove
+
+- name: remove all configuration files, which should be removed
+ ansible.builtin.file:
+ path: "{{ item.path }}"
+ state: absent
+ become: true
+ loop: "{{ docker_compose__config_files_to_remove.files }}"
+ notify: docker compose down
+
+- name: make sure all given configuration files are deployed
+ ansible.builtin.copy:
+ content: "{{ item.content }}"
+ dest: "/ansible_docker_compose/configs/{{ item.name }}"
+ mode: "0644"
+ owner: root
+ group: root
+ become: true
+ loop: "{{ docker_compose__configuration_files }}"
+ notify: docker compose down
+
+- name: Flush handlers to make "docker compose down" handler run now
+ ansible.builtin.meta: flush_handlers
+
+- name: docker compose ps --format json before docker compose up
+ ansible.builtin.command:
+ cmd: /usr/bin/docker compose ps --format json
+ chdir: /ansible_docker_compose
+ become: true
+ changed_when: false
+ register: docker_compose__ps_json_before_up
+
+- name: docker compose up --detach --pull always
+ ansible.builtin.command:
+ cmd: /usr/bin/docker compose up --detach --pull always
+ chdir: /ansible_docker_compose
+ become: true
+ changed_when: false
+ # The changed for this task is tried to be determined by the "potentially
+ # report changed" task together with the "docker compose ps --format json
+ # [...]" tasks.
+
+- name: docker compose ps --format json after docker compose up
+ ansible.builtin.command:
+ cmd: /usr/bin/docker compose ps --format json
+ chdir: /ansible_docker_compose
+ become: true
+ changed_when: false
+ register: docker_compose__ps_json_after_up
+
+- name: potentially report changed
+ ansible.builtin.debug:
+ msg: "If this reports changed, then the docker compose containers changed."
+ changed_when: (docker_compose__ps_json_before_up.stdout | from_json | community.general.json_query('[].ID') | sort)
+ != (docker_compose__ps_json_after_up.stdout | from_json | community.general.json_query('[].ID') | sort)