diff --git a/ansible/group_vars/all/infra-vms b/ansible/group_vars/all/infra-vms
index 0d197791d9860fd354d55ac38f2ed3280f689f64..35b7baa2fa6404d92f7899e57ac71fedfdc92b36 100644
--- a/ansible/group_vars/all/infra-vms
+++ b/ansible/group_vars/all/infra-vms
@@ -122,8 +122,8 @@ infra_vm_lvm_groups_extra: []
 
 # Whether a 'data' LVM volume group should exist on the infrastructure vm. By
 # default this contains a 'docker-volumes' logical volume for Docker volume
-# storage. It will also be used for Docker container and image storage if #
-# 'docker_storage_driver' is set to 'devicemapper'. Default is true if #
+# storage. It will also be used for Docker container and image storage if
+# 'docker_storage_driver' is set to 'devicemapper'. Default is true if
 # 'docker_storage_driver' is set to 'devicemapper', or false otherwise.
 infra_vm_lvm_group_data_enabled: "{{ docker_storage_driver == 'devicemapper' }}"
 
diff --git a/ansible/infra-vm-provision.yml b/ansible/infra-vm-provision.yml
index 35eba3fa4e0a32bcf54da95fa9b1694570bf4ae5..1e4878154f3ec25fe1cf1b13a6e50bc598775b8b 100644
--- a/ansible/infra-vm-provision.yml
+++ b/ansible/infra-vm-provision.yml
@@ -42,3 +42,4 @@
         # https://github.com/ansible/ansible/issues/49254
         ansible_ssh_extra_args: '{{ infra_vm_wait_connection_ssh_extra_args }}'
         ansible_user: "{{ bootstrap_user }}"
+        ansible_python_interpreter: /usr/bin/python3
diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst
index dc50c117e2e956bc91b9a7e7ee13da3552f747ed..5f5f6210d7c326fca4c87fbc1dbc6265a8440726 100644
--- a/doc/source/architecture.rst
+++ b/doc/source/architecture.rst
@@ -15,6 +15,10 @@ Seed host
     The seed host runs the bifrost deploy container and is used to provision
     the cloud hosts.  By default, container images are built on the seed.
     Typically the seed host is deployed as a VM but this is not mandatory.
+Infrastructure VM hosts
+    Infrastructure VMs (or Infra VMs) are virtual machines that may be deployed
+    to provide supplementary infrastructure services. They may be for things
+    like proxies or DNS servers that are dependencies of the Cloud hosts.
 Cloud hosts
     The cloud hosts run the OpenStack control plane, network, monitoring,
     storage, and virtualised compute services.  Typically the cloud hosts run
diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst
index de19ab6315c9afc1013bccca20176100581cf2ef..afd1477b17ca0108abd3c2c1df16179282d91243 100644
--- a/doc/source/configuration/reference/hosts.rst
+++ b/doc/source/configuration/reference/hosts.rst
@@ -9,6 +9,7 @@ deployment of containers. Hosts that are configured by Kayobe include:
 
 * Seed hypervisor (``kayobe seed hypervisor host configure``)
 * Seed (``kayobe seed host configure``)
+* Infra VMs (``kayobe infra vm host configure``)
 * Overcloud (``kayobe overcloud host configure``)
 
 Unless otherwise stated, all host configuration described here is applied to
@@ -29,6 +30,7 @@ following files under ``${KAYOBE_CONFIG_PATH}``:
 * ``seed.yml``
 * ``compute.yml``
 * ``controller.yml``
+* ``infra-vms.yml``
 * ``monitoring.yml``
 * ``storage.yml``
 
@@ -82,6 +84,7 @@ variable, but may be set via the following variables:
 
 * ``seed_hypervisor_bootstrap_user``
 * ``seed_bootstrap_user``
+* ``infra_vm_bootstrap_user``
 * ``compute_bootstrap_user``
 * ``controller_bootstrap_user``
 * ``monitoring_bootstrap_user``
@@ -179,6 +182,7 @@ variables can be used to set the users for specific types of hosts:
 
 * ``seed_hypervisor_users``
 * ``seed_users``
+* ``infra_vm_users``
 * ``compute_users``
 * ``controller_users``
 * ``monitoring_users``
@@ -435,6 +439,7 @@ variables can be used to set ``sysctl`` configuration specific types of hosts:
 
 * ``seed_hypervisor_sysctl_parameters``
 * ``seed_sysctl_parameters``
+* ``infra_vm_sysctl_parameters``
 * ``compute_sysctl_parameters``
 * ``controller_sysctl_parameters``
 * ``monitoring_sysctl_parameters``
@@ -559,6 +564,7 @@ convenience, this is mapped to the following variables:
 
 * ``seed_hypervisor_mdadm_arrays``
 * ``seed_mdadm_arrays``
+* ``infra_vm_mdadm_arrays``
 * ``compute_mdadm_arrays``
 * ``controller_mdadm_arrays``
 * ``monitoring_mdadm_arrays``
@@ -594,6 +600,7 @@ convenience, this is mapped to the following variables:
 
 * ``seed_hypervisor_luks_devices``
 * ``seed_luks_devices``
+* ``infra_vm_luks_devices``
 * ``compute_luks_devices``
 * ``controller_luks_devices``
 * ``monitoring_luks_devices``
@@ -630,6 +637,7 @@ this is mapped to the following variables:
 
 * ``seed_hypervisor_lvm_groups``
 * ``seed_lvm_groups``
+* ``infra_vm_lvm_groups``
 * ``compute_lvm_groups``
 * ``controller_lvm_groups``
 * ``monitoring_lvm_groups``
@@ -670,6 +678,7 @@ This configuration is enabled by the following variables, which default to
 * ``compute_lvm_group_data_enabled``
 * ``controller_lvm_group_data_enabled``
 * ``seed_lvm_group_data_enabled``
+* ``infra_vm_lvm_group_data_enabled``
 * ``storage_lvm_group_data_enabled``
 
 These variables can be set to ``true`` to enable the data volume group if the
@@ -680,6 +689,7 @@ To use this configuration, a list of disks must be configured via the following
 variables:
 
 * ``seed_lvm_group_data_disks``
+* ``infra_vm_lvm_group_data_disks``
 * ``compute_lvm_group_data_disks``
 * ``controller_lvm_group_data_disks``
 * ``monitoring_lvm_group_data_disks``
@@ -698,6 +708,7 @@ The Docker volumes LVM volume is assigned a size given by the following
 variables, with a default value of 75% (of the volume group's capacity):
 
 * ``seed_lvm_group_data_lv_docker_volumes_size``
+* ``infra_vm_lvm_group_data_lv_docker_volumes_size``
 * ``compute_lvm_group_data_lv_docker_volumes_size``
 * ``controller_lvm_group_data_lv_docker_volumes_size``
 * ``monitoring_lvm_group_data_lv_docker_volumes_size``
@@ -728,6 +739,7 @@ To define additional logical logical volumes in the default ``data`` volume
 group, modify one of the following variables:
 
 * ``seed_lvm_group_data_lvs``
+* ``infra_vm_lvm_group_data_lvs``
 * ``compute_lvm_group_data_lvs``
 * ``controller_lvm_group_data_lvs``
 * ``monitoring_lvm_group_data_lvs``
@@ -752,6 +764,7 @@ It is possible to define additional LVM volume groups via the following
 variables:
 
 * ``seed_lvm_groups_extra``
+* ``infra_vm_lvm_groups_extra``
 * ``compute_lvm_groups_extra``
 * ``controller_lvm_groups_extra``
 * ``monitoring_lvm_groups_extra``
diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst
index 7b868501a9b409d064391d751369fbf6f66baa62..0149a574d7ddb449cd76e1af4c161c5850d86321 100644
--- a/doc/source/configuration/reference/network.rst
+++ b/doc/source/configuration/reference/network.rst
@@ -800,6 +800,18 @@ networks to attach.  Alternatively, the list may be
 completely overridden by setting ``seed_hypervisor_network_interfaces``.  These
 variables are found in ``${KAYOBE_CONFIG_PATH}/seed-hypervisor.yml``.
 
+Infra VMs
+---------
+
+By default, infrastructure VMs are attached to the following network:
+
+* overcloud admin network
+
+This list may be extended by setting ``infra_vm_extra_network_interfaces`` to a
+list of names of additional networks to attach.  Alternatively, the list may be
+completely overridden by setting ``infra_vm_network_interfaces``.  These
+variables are found in ``${KAYOBE_CONFIG_PATH}/infra-vms.yml``.
+
 Controllers
 -----------
 
diff --git a/etc/kayobe/infra-vms.yml b/etc/kayobe/infra-vms.yml
index e5762b16117a6c38048905d88e9aa4817508c984..b9457448bbca0ce2e2813691a5224b1a5bc5d2ca 100644
--- a/etc/kayobe/infra-vms.yml
+++ b/etc/kayobe/infra-vms.yml
@@ -53,7 +53,7 @@
 #infra_vm_wait_connection_ssh_extra_args:
 
 # OS family. Needed for config drive generation.
-# infra_vm_os_family:
+#infra_vm_os_family:
 
 ###############################################################################
 # Infrastructure VM node configuration.
diff --git a/etc/kayobe/inventory/group_vars/infra-vms/ansible-python-interpreter b/etc/kayobe/inventory/group_vars/infra-vms/ansible-python-interpreter
new file mode 100644
index 0000000000000000000000000000000000000000..54abbf23cb33fab54937d38dc76d0ddb62968725
--- /dev/null
+++ b/etc/kayobe/inventory/group_vars/infra-vms/ansible-python-interpreter
@@ -0,0 +1,3 @@
+---
+# Use a virtual environment for remote operations.
+ansible_python_interpreter: "{{ virtualenv_path }}/kayobe/bin/python"
diff --git a/etc/kayobe/inventory/group_vars/infra-vms/network-interfaces b/etc/kayobe/inventory/group_vars/infra-vms/network-interfaces
new file mode 100644
index 0000000000000000000000000000000000000000..015413ded0e6c12dd582ca9fddf97f717a057843
--- /dev/null
+++ b/etc/kayobe/inventory/group_vars/infra-vms/network-interfaces
@@ -0,0 +1,12 @@
+---
+###############################################################################
+# Network interface definitions for the infra-vms group.
+
+# Overcloud provisioning network IP information.
+# provision_oc_net_interface:
+# provision_oc_net_bridge_ports:
+# provision_oc_net_bond_slaves:
+
+###############################################################################
+# Dummy variable to allow Ansible to accept this file.
+workaround_ansible_issue_8743: yes