diff --git a/.ansible-docs.yml.bak b/.ansible-docs.yml.bak
new file mode 100644
index 0000000000000000000000000000000000000000..4ebe93a23f36dc158284b6a0ac053264eb55d6c7
--- /dev/null
+++ b/.ansible-docs.yml.bak
@@ -0,0 +1,23 @@
+---
+output_file: README.md
+output_mode: inject
+output_template: |
+  <!-- BEGIN_ANSIBLE_DOCS -->
+
+  This is my role: {{ role }}
+
+  <!-- 'metadata' contains all the things in meta/main.yml -->
+  {{ metadata.galaxy_info.license }}
+
+  <!-- All the usual Jinja2 filters are available -->
+  {{ metadata.galaxy_info.galaxy_tags | sort }}
+
+  <!-- Including files is also possible in relation to the role's directory with Jinja2's include directive -->
+  {% include "defaults/main.yml" %}
+
+  <!-- 'argument_specs' contains all the things in meta/argument_specs.yml -->
+  {% for entrypoint, specs in argument_specs | items %}
+  Task file name: {{ entrypoint }}.yml has {{ specs | length }} input variables!
+  {% endfor %}
+  <!-- END_ANSIBLE_DOCS -->
+...
diff --git a/.ansible-lint b/.ansible-lint
index 18375a1b4cfcb2c47e4c99ba3121f600b099bdf0..0920b1bee19f18811d2cb73e4ae8fe23c83525b3 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -1,8 +1,15 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
 # https://ansible-lint.readthedocs.io/rules/
 skip_list:
-  - args[module]
-  - jinja[spacing]
   - key-order[task]
   - name[template]
   - package-latest
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f5f5786e98509b7ff15e9dd49b8b9c37bfe5ba15..1f4a1d7ca8124502d13ce8073a45d9fb22ca06c0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,19 +8,44 @@
 #
 # You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
-image: registry.git.coop/webarch/containers/images/ansible:0.24.0
 variables:
-  ANSIBLE_DEFAULT_VERBOSITY: "2"
-  ANSIBLE_DIFF_ALWAYS: "1"
+  ANSIBLE_DISPLAY_SKIPPED_HOSTS: "0"
   ANSIBLE_FORCE_COLOR: "1"
+  GIT_STRATEGY: none
+  MOLECULE_VERBOSITY: "0"
   PY_COLORS: "1"
 before_script:
-  - chmod 700 $(pwd)
+  - cd
+  - source ~/.profile
+  - mkdir -p builds/${CI_PROJECT_ROOT_NAMESPACE}
+  - cd builds/${CI_PROJECT_ROOT_NAMESPACE}
+  - git clone ${CI_PROJECT_URL}.git
+  - ansible-galaxy install -r ${CI_PROJECT_NAME}/meta/requirements.yml
+  - cd ${CI_PROJECT_NAME}
+  - cat /etc/debian_version
+  - which ansible
+  - ansible --version
+  - which yamllint
+  - yamllint --version
+  - which molecule
+  - molecule --version
 stages:
-  - lint
-lint:
-  stage: lint
+  - bookworm
+  - bullseye
+  - jammy
+bookworm:
+  image: registry.git.coop/webarch/containers/images/bookworm:20230605
+  stage: bookworm
   script:
-    - molecule converge
-# vim: syntax=yaml
+    - molecule test
+bullseye:
+  image: registry.git.coop/webarch/containers/images/bullseye:20230605
+  stage: bullseye
+  script:
+    - molecule test
+jammy:
+  image: registry.git.coop/webarch/containers/images/jammy:20230605
+  stage: jammy
+  script:
+    - molecule test
 ...
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3ebd0b5f2e23b11d60b66010b7fecfbf4f56b80f..44677b9992c347e615927427ed7ab49a8c25b9ac 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,21 +8,29 @@
 #
 # You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
-# https://yamllint.readthedocs.io/en/stable/integration.html
-# https://github.com/adrienverge/yamllint/tags
 repos:
+  # https://github.com/adrienverge/yamllint/tags
   - repo: https://github.com/adrienverge/yamllint.git
     rev: v1.32.0
     hooks:
       - id: yamllint
         name: YAML Lint
+  # https://github.com/ansible/ansible-lint/releases
   - repo: https://github.com/ansible/ansible-lint.git
-    rev: v6.17.0
+    rev: v6.17.2
     hooks:
       - id: ansible-lint
         name: Ansible Lint
         language: python
         additional_dependencies:
+          # https://github.com/kellyjonbrazil/jc/releases
           - jc==1.23.2
+          # https://github.com/jmespath/jmespath.py/tags
           - jmespath==1.0.1
+  # https://github.com/jackdewinter/pymarkdown/releases
+  - repo: https://github.com/jackdewinter/pymarkdown
+    rev: v0.9.11
+    hooks:
+      - id: pymarkdown
+        name: Markdown Lint
 ...
diff --git a/.pymarkdown b/.pymarkdown
new file mode 100644
index 0000000000000000000000000000000000000000..e70c272443a1ac0f2c8ab7a126423fcb5ee02dce
--- /dev/null
+++ b/.pymarkdown
@@ -0,0 +1,7 @@
+}
+  "plugins": {
+    "md013": {
+      "enabled": false
+    }
+  }
+}
diff --git a/meta/requirements.yml b/meta/requirements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eb016f613190fc1924a2d8535e169c0b7cda27b5
--- /dev/null
+++ b/meta/requirements.yml
@@ -0,0 +1,15 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: apt
+  src: https://git.coop/webarch/apt.git
+  version: 2.2.3  # apt
+  scm: git
+...
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index 699f311d6ae08ef208a8c55e240bc4e1d0c9e2e5..1bfeed59868a757bcdac0681550954e2a15e9f82 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -8,33 +8,21 @@
 #
 # You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
-- name: Lint
-  become: false
+- name: Run as root
+  become: true
   connection: local
-  gather_facts: false
+  gather_facts: true
   hosts:
     - localhost
   tasks:
 
-    - name: Check Jinja2 syntax
-      ansible.builtin.command: find -type f -name '*.j2' -exec ansiblej2lint.py {} +
-      check_mode: false
-      args:
-        chdir: ../..
-      changed_when: false
+    - name: Debug ansible_effective_user_id
+      ansible.builtin.debug:
+        var: ansible_effective_user_id
 
-    - name: YAML lint
-      ansible.builtin.command: yamllint -f colored -c .yamllint .
-      check_mode: false
-      args:
-        chdir: ../..
-      changed_when: false
-
-    - name: Ansible lint
-      ansible.builtin.command: ansible-lint -c .ansible-lint --force-color .
-      check_mode: false
-      args:
-        chdir: ../..
-      changed_when: false
-# vim: syntax=yaml
+    - name: Include php role as root with install set to deb
+      ansible.builtin.include_role:
+        name: php
+      vars:  # noqa var-naming[no-role-prefix]
+        php: true
 ...
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 9cacaf51b0366fb6c527da2bdf20afc1defa36b9..1e891441d9198b1a33c0e2dec0886962abe49a6f 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -16,5 +16,4 @@ provisioner:
   name: ansible
 verifier:
   name: ansible
-# vim: syntax=yaml
 ...
diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml
new file mode 100644
index 0000000000000000000000000000000000000000..366c75a3140a0ddb70816fa4466bb0933353a5c7
--- /dev/null
+++ b/molecule/default/verify.yml
@@ -0,0 +1,28 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: Verify as root
+  become: true
+  connection: local
+  gather_facts: true
+  hosts:
+    - localhost
+  tasks:
+
+    - name: Run which php
+      ansible.builtin.command: which php
+      check_mode: false
+      changed_when: false
+      register: molecule_root_which_php
+
+    - name: Debug which php
+      ansible.builtin.debug:
+        var: molecule_root_which_php.stdout
+...
diff --git a/tasks/pkg.yml b/tasks/pkg.yml
index a059d073b6d2846e0b25d51c7b80a73ed229827c..d693efde58f7b09c0abab60b8effe60ff68724f0 100644
--- a/tasks/pkg.yml
+++ b/tasks/pkg.yml
@@ -19,7 +19,7 @@
 
     - name: Set a fact for installed PHP packages that are to be removed
       ansible.builtin.set_fact:
-        php_pkg_remove: "{{ ansible_local.dpkg.installed | ansible.builtin.intersect(php_pkg_absent) | default ([]) }}"
+        php_pkg_remove: "{{ ansible_local.dpkg.installed | ansible.builtin.intersect(php_pkg_absent) | default([]) }}"
 
     - name: Debug installed PHP packages that are to be removed
       ansible.builtin.debug:
diff --git a/tasks/verify.yml b/tasks/verify.yml
index 99212a38eedbb03a30c5c26956f6f445f147bf01..645431873972927fa8bfedcb92d8a529c5fc1f9a 100644
--- a/tasks/verify.yml
+++ b/tasks/verify.yml
@@ -23,7 +23,7 @@
 
     - name: Check php_ variables using meta/argument_specs.yml
       ansible.builtin.validate_argument_spec:
-        argument_spec: "{{ (lookup('ansible.builtin.file', 'meta/argument_specs.yml') | from_yaml )['argument_specs']['main']['options'] }}"
+        argument_spec: "{{ (lookup('ansible.builtin.file', 'meta/argument_specs.yml') | from_yaml)['argument_specs']['main']['options'] }}"
         provided_arguments: "{{ phphostvars }}"
 
   when: