diff --git a/ansible/group_vars/all/overcloud-dib b/ansible/group_vars/all/overcloud-dib
new file mode 100644
index 0000000000000000000000000000000000000000..0f8d76f5b17990047399f58c7a88ba59786cc1f2
--- /dev/null
+++ b/ansible/group_vars/all/overcloud-dib
@@ -0,0 +1,54 @@
+---
+# Overcloud host disk image configuration.
+
+###############################################################################
+# Diskimage-builder configuration for overcloud host disk images.
+
+# Whether to build host disk images with DIB directly instead of through
+# Bifrost. Setting it to true disables Bifrost image build and allows images to
+# be built with the `kayobe overcloud host image build` command. Default value
+# is False. This will change in a future release.
+overcloud_dib_build_host_images: False
+
+# DIB base OS element. Default is {{ os_distribution }}.
+overcloud_dib_os_element: "{{ os_distribution }}"
+
+# DIB image OS release. Default is {{ os_release }}.
+overcloud_dib_os_release: "{{ os_release }}"
+
+# List of default DIB elements. Default is ["centos", "cloud-init-datasources",
+# "disable-selinux", "enable-serial-console", "vm"] when
+# overcloud_dib_os_element is "centos", or ["ubuntu", "cloud-init-datasources",
+# "enable-serial-console", "vm"] when overcloud_dib_os_element is "ubuntu".
+overcloud_dib_elements_default:
+  - "{{ overcloud_dib_os_element }}"
+  - "cloud-init-datasources"
+  - "{% if overcloud_dib_os_element == 'centos' %}disable-selinux{% endif %}"
+  - "enable-serial-console"
+  - "vm"
+
+# List of additional DIB elements. Default is none.
+overcloud_dib_elements_extra: []
+
+# List of DIB elements. Default is a combination of
+# overcloud_dib_elements_default and overcloud_dib_elements_extra.
+overcloud_dib_elements: "{{ overcloud_dib_elements_default | select | list + overcloud_dib_elements_extra }}"
+
+# DIB default environment variables. Default is
+# {"DIB_BOOTLOADER_DEFAULT_CMDLINE": "nofb nomodeset gfxpayload=text
+# net.ifnames=1", "DIB_CLOUD_INIT_DATASOURCES": "ConfigDrive", "DIB_RELEASE":
+# "{{ overcloud_dib_os_release }}"}.
+overcloud_dib_env_vars_default:
+  DIB_BOOTLOADER_DEFAULT_CMDLINE: "nofb nomodeset gfxpayload=text net.ifnames=1"
+  DIB_CLOUD_INIT_DATASOURCES: "ConfigDrive"
+  DIB_RELEASE: "{{ overcloud_dib_os_release }}"
+
+# DIB additional environment variables. Default is none.
+overcloud_dib_env_vars_extra: {}
+
+# DIB environment variables. Default is combination of
+# overcloud_dib_env_vars_default and overcloud_dib_env_vars_extra.
+overcloud_dib_env_vars: "{{ overcloud_dib_env_vars_default | combine(overcloud_dib_env_vars_extra) }}"
+
+# List of DIB packages to install. Default is to install no extra packages.
+overcloud_dib_packages: []
diff --git a/ansible/overcloud-host-image-build.yml b/ansible/overcloud-host-image-build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1855607cc865e9cb5033fd39d3d380caeb21507a
--- /dev/null
+++ b/ansible/overcloud-host-image-build.yml
@@ -0,0 +1,51 @@
+---
+# Build and install a overcloud host disk image for the seed host's ironic
+# service.
+
+- name: Ensure overcloud host disk image is built and installed
+  hosts: seed
+  tags:
+    - overcloud-host-image-build
+  vars:
+    overcloud_host_image_name: "deployment_image"
+    overcloud_host_disk_images:
+      - "{{ overcloud_host_image_name }}.qcow2"
+    overcloud_host_image_force_rebuild: False
+  tasks:
+    - block:
+        - name: Ensure overcloud host disk image is built
+          include_role:
+            name: stackhpc.os-images
+          vars:
+            os_images_venv: "{{ virtualenv_path }}/overcloud-host-image-dib"
+            os_images_package_state: latest
+            os_images_upper_constraints_file: "{{ pip_upper_constraints_file }}"
+            os_images_cache: "{{ image_cache_path }}"
+            os_images_common: ""
+            os_images_list:
+              - name: "{{ overcloud_host_image_name }}"
+                elements: "{{ overcloud_dib_elements }}"
+                env: "{{ overcloud_dib_env_vars }}"
+                packages: "{{ overcloud_dib_packages }}"
+                type: qcow2
+            os_images_upload: False
+            os_images_force_rebuild: "{{ overcloud_host_image_force_rebuild }}"
+
+        - name: Ensure overcloud host disk image is copied onto seed
+          copy:
+            src: "{{ image_cache_path }}/{{ overcloud_host_image_name }}/{{ item }}"
+            dest: "/etc/kolla/bifrost/{{ item }}"
+            remote_src: True
+          with_items: "{{ overcloud_host_disk_images }}"
+          become: True
+
+        - name: Copy overcloud host disk image into /httpboot
+          command: >
+            docker exec bifrost_deploy
+            bash -c 'ansible -vvvv target
+            -i /bifrost/playbooks/inventory/target
+            -m copy
+            -a "src=/etc/bifrost/{{ item }} dest=/httpboot/{{ item }}"
+            -e "ansible_python_interpreter=/var/lib/kolla/venv/bin/python"'
+          with_items: "{{ overcloud_host_disk_images }}"
+      when: overcloud_dib_build_host_images | bool
diff --git a/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml
index e3456414f5ff050b5612182b4d06703485c92a13..a4fe0522b6b28e1653fe31f0a27a9ad7c933c1f1 100644
--- a/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml
+++ b/ansible/roles/kolla-bifrost/templates/kolla/config/bifrost/dib.yml
@@ -1,4 +1,5 @@
 ---
+{% if not overcloud_dib_build_host_images | bool %}
 # Diskimage-builder element for base OS.
 dib_os_element: "{{ kolla_bifrost_dib_os_element }}"
 
@@ -13,3 +14,9 @@ dib_elements: "{{ (kolla_bifrost_dib_elements + [kolla_bifrost_dib_init_element]
 
 # List of DIB image packages.
 dib_packages: "{{ kolla_bifrost_dib_packages | join(',') }}"
+{% else %}
+# Stop building overcloud host image using Bifrost. This needs to be defined
+# here to override the default true value set in kolla-ansible in
+# ansible/roles/bifrost/templates/dib.yml.j2.
+create_image_via_dib: False
+{% endif %}
diff --git a/ansible/seed-ipa-build.yml b/ansible/seed-ipa-build.yml
index 05297f33251372803e94bd22f28159880acae1f8..1145bf819db6b36bce81078835caf0c20bcee0a5 100644
--- a/ansible/seed-ipa-build.yml
+++ b/ansible/seed-ipa-build.yml
@@ -53,8 +53,8 @@
         - name: Copy Ironic Python Agent images into /httpboot
           command: >
             docker exec bifrost_deploy
-            bash -c 'export OS_CLOUD=bifrost &&
-            ansible -vvvv target -i /bifrost/playbooks/inventory/target
+            bash -c 'ansible -vvvv target
+            -i /bifrost/playbooks/inventory/target
             -m copy
             -a "src=/etc/bifrost/{{ item }} dest=/httpboot/{{ item }}"
             -e "ansible_python_interpreter=/var/lib/kolla/venv/bin/python"'
diff --git a/dev/functions b/dev/functions
index 90bfe3c94612e6fa295a92a4da6f32f1e4b53370..0e9940238b049b5ec8f75843ca69421d10ab4367 100644
--- a/dev/functions
+++ b/dev/functions
@@ -219,6 +219,11 @@ function is_ironic_enabled {
     [[ $ironic_enabled =~ ^true$ ]]
 }
 
+function is_overcloud_host_image_built_by_dib {
+    overcloud_dib_build_host_images=$(kayobe configuration dump --host controllers[0] --var-name overcloud_dib_build_host_images)
+    [[ $overcloud_dib_build_host_images =~ ^true$ ]]
+}
+
 function environment_setup {
     # NOTE: Virtualenv's activate script references an unbound variable.
     set +u
@@ -316,6 +321,13 @@ function seed_deploy {
     else
         echo "Not building seed deployment images"
     fi
+
+    if is_overcloud_host_image_built_by_dib; then
+        echo "Building overcloud host images"
+        run_kayobe overcloud host image build
+    else
+        echo "Not building overcloud host images"
+    fi
 }
 
 function seed_upgrade {
diff --git a/etc/kayobe/overcloud-dib.yml b/etc/kayobe/overcloud-dib.yml
new file mode 100644
index 0000000000000000000000000000000000000000..41bce702abca0fe7c985dd01077bfbbf4c0a0ba5
--- /dev/null
+++ b/etc/kayobe/overcloud-dib.yml
@@ -0,0 +1,50 @@
+---
+# Overcloud host disk image configuration.
+
+###############################################################################
+# Diskimage-builder configuration for overcloud host disk images.
+
+# Whether to build host disk images with DIB directly instead of through
+# Bifrost. Setting it to true disables Bifrost image build and allows images to
+# be built with the `kayobe overcloud host image build` command. Default value
+# is False. This will change in a future release.
+#overcloud_dib_build_host_images:
+
+# DIB base OS element. Default is {{ os_distribution }}.
+#overcloud_dib_os_element:
+
+# DIB image OS release. Default is {{ os_release }}.
+#overcloud_dib_os_release:
+
+# List of default DIB elements. Default is ["centos", "cloud-init-datasources",
+# "disable-selinux", "enable-serial-console", "vm"] when
+# overcloud_dib_os_element is "centos", or ["ubuntu", "cloud-init-datasources",
+# "enable-serial-console", "vm"] when overcloud_dib_os_element is "ubuntu".
+#overcloud_dib_elements_default:
+
+# List of additional DIB elements. Default is none.
+#overcloud_dib_elements_extra:
+
+# List of DIB elements. Default is a combination of
+# overcloud_dib_elements_default and overcloud_dib_elements_extra.
+#overcloud_dib_elements:
+
+# DIB default environment variables. Default is
+# {"DIB_BOOTLOADER_DEFAULT_CMDLINE": "nofb nomodeset gfxpayload=text
+# net.ifnames=1", "DIB_CLOUD_INIT_DATASOURCES": "ConfigDrive", "DIB_RELEASE":
+# "{{ overcloud_dib_os_release }}"}.
+#overcloud_dib_env_vars_default:
+
+# DIB additional environment variables. Default is none.
+#overcloud_dib_env_vars_extra:
+
+# DIB environment variables. Default is combination of
+# overcloud_dib_env_vars_default and overcloud_dib_env_vars_extra.
+#overcloud_dib_env_vars:
+
+# List of DIB packages to install. Default is to install no extra packages.
+#overcloud_dib_packages:
+
+###############################################################################
+# Dummy variable to allow Ansible to accept this file.
+workaround_ansible_issue_8743: yes
diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py
index 066f8e666272fbfac06b7bd0e11b17a7fe73bcf0..b992bc6e3e1d795bf8557bbc7e5cfcc6d42e27f0 100644
--- a/kayobe/cli/commands.py
+++ b/kayobe/cli/commands.py
@@ -1762,6 +1762,31 @@ class OvercloudDeploymentImageBuild(KayobeAnsibleMixin, VaultMixin, Command):
                                   extra_vars=extra_vars)
 
 
+class OvercloudHostImageBuild(KayobeAnsibleMixin, VaultMixin, Command):
+    """Build overcloud host disk images.
+
+    Builds host disk images using Diskimage Builder (DIB) for use when
+    provisioning the overcloud hosts.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super(OvercloudHostImageBuild, self).get_parser(
+            prog_name)
+        group = parser.add_argument_group("Host Image Build")
+        group.add_argument("--force-rebuild", action="store_true",
+                           help="whether to force rebuilding the images")
+        return parser
+
+    def take_action(self, parsed_args):
+        self.app.LOG.debug("Building overcloud host disk images")
+        playbooks = _build_playbook_list("overcloud-host-image-build")
+        extra_vars = {}
+        if parsed_args.force_rebuild:
+            extra_vars["overcloud_host_image_force_rebuild"] = True
+        self.run_kayobe_playbooks(parsed_args, playbooks,
+                                  extra_vars=extra_vars)
+
+
 class OvercloudPostConfigure(KayobeAnsibleMixin, VaultMixin, Command):
     """Perform post-deployment configuration.
 
diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py
index f40144f4aaf443c4522e016e4e6be1392177fccc..73bb7656a0bc0f280c8f31a9d4a204dd9031facd 100644
--- a/kayobe/tests/unit/cli/test_commands.py
+++ b/kayobe/tests/unit/cli/test_commands.py
@@ -2000,6 +2000,50 @@ class TestCase(unittest.TestCase):
         ]
         self.assertEqual(expected_calls, mock_run.call_args_list)
 
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_overcloud_host_image_build(self, mock_run):
+        command = commands.OvercloudHostImageBuild(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args([])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    utils.get_data_files_path(
+                        "ansible", "overcloud-host-image-build.yml"),
+                ],
+                extra_vars={},
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_overcloud_host_image_build_force_rebuild(self, mock_run):
+        command = commands.OvercloudHostImageBuild(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--force-rebuild"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    utils.get_data_files_path(
+                        "ansible", "overcloud-host-image-build.yml"),
+                ],
+                extra_vars={"overcloud_host_image_force_rebuild": True},
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
     @mock.patch.object(commands.KayobeAnsibleMixin,
                        "run_kayobe_playbooks")
     def test_overcloud_deployment_image_build(self, mock_run):
diff --git a/playbooks/kayobe-seed-base/overrides.yml.j2 b/playbooks/kayobe-seed-base/overrides.yml.j2
index 0881ebff61deb0852df3f49ed72eace839428c9d..5c9c948c28621b63c202d9af88e8ca624bce5517 100644
--- a/playbooks/kayobe-seed-base/overrides.yml.j2
+++ b/playbooks/kayobe-seed-base/overrides.yml.j2
@@ -35,4 +35,7 @@ aio_bridge_ports:
 # Build seed deployment images (IPA) with extra-hardware element
 ipa_build_images: true
 ipa_build_dib_elements_extra:
-  - "extra-hardware"
\ No newline at end of file
+  - "extra-hardware"
+
+# Build overcloud host image
+overcloud_dib_build_host_images: true
diff --git a/roles/kayobe-diagnostics/files/get_logs.sh b/roles/kayobe-diagnostics/files/get_logs.sh
index d07f8680aa389161788fa24bd542f939dc45fb43..539d41a1145c2c633a4b11ce08d0191b19f0e76b 100644
--- a/roles/kayobe-diagnostics/files/get_logs.sh
+++ b/roles/kayobe-diagnostics/files/get_logs.sh
@@ -102,6 +102,12 @@ copy_logs() {
         cp /opt/kayobe/images/ipa/ipa.stderr /opt/kayobe/images/ipa/ipa.stdout ${LOG_DIR}/kayobe/
     fi
 
+    # Overcloud host image build logs
+    if [[ -f /opt/kayobe/images/deployment_image/deployment_image.stderr ]] || [[ -f /opt/kayobe/images/deployment_image/deployment_image.stdout ]]; then
+        mkdir -p ${LOG_DIR}/kayobe
+        cp /opt/kayobe/images/deployment_image/deployment_image.stderr /opt/kayobe/images/deployment_image/deployment_image.stdout ${LOG_DIR}/kayobe/
+    fi
+
     # Rename files to .txt; this is so that when displayed via
     # logs.openstack.org clicking results in the browser shows the
     # files, rather than trying to send it to another app or make you
diff --git a/setup.cfg b/setup.cfg
index 209fdc6042836000ff1ee611e60845d5e65f8072..766398d352f2b94f427761425f300b4846d4911a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -61,6 +61,7 @@ kayobe.cli=
     overcloud_facts_gather = kayobe.cli.commands:OvercloudFactsGather
     overcloud_hardware_inspect = kayobe.cli.commands:OvercloudHardwareInspect
     overcloud_host_configure = kayobe.cli.commands:OvercloudHostConfigure
+    overcloud_host_image_build = kayobe.cli.commands:OvercloudHostImageBuild
     overcloud_host_package_update = kayobe.cli.commands:OvercloudHostPackageUpdate
     overcloud_host_command_run = kayobe.cli.commands:OvercloudHostCommandRun
     overcloud_host_upgrade = kayobe.cli.commands:OvercloudHostUpgrade
@@ -148,6 +149,8 @@ kayobe.cli.overcloud_hardware_inspect =
     hooks = kayobe.cli.commands:HookDispatcher
 kayobe.cli.overcloud_host_configure =
     hooks = kayobe.cli.commands:HookDispatcher
+kayobe.cli.overcloud_host_image_build =
+    hooks = kayobe.cli.commands:HookDispatcher
 kayobe.cli.overcloud_host_package_update =
     hooks = kayobe.cli.commands:HookDispatcher
 kayobe.cli.overcloud_host_command_run =