diff --git a/README.md b/README.md
index 819b4f7b5897b51a5a65e3a31ef2406a0cd7a76f..0189548b59eff04f8037aa6198a8c69aab448311 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,13 @@ Image Builder (DIB). To configure the seed host OS:
 
     (kayobe-venv) $ kayobe seed host configure
 
+It is possible to use prebuilt container images from an image registry such as
+Dockerhub. In some cases it may be necessary to build images locally either to
+apply local image customisation or to use a downstream version of Kolla. To
+build images locally:
+
+    (kayobe-venv) $ kayobe seed container image build
+
 To deploy the seed services in containers:
 
     (kayobe-venv) $ kayobe seed service deploy
@@ -152,6 +159,13 @@ provisioned with an OS image. To configure the overcloud hosts' OS:
 
     (kayobe-venv) $ kayobe overcloud host configure
 
+It is possible to use prebuilt container images from an image registry such as
+Dockerhub. In some cases it may be necessary to build images locally either to
+apply local image customisation or to use a downstream version of Kolla. To
+build images locally:
+
+    (kayobe-venv) $ kayobe overcloud container image build
+
 To deploy the overcloud services in containers:
 
     (kayobe-venv) $ kayobe overcloud service deploy
@@ -166,11 +180,11 @@ that can be used to access the OpenStack services:
 
 To run an arbitrary Kayobe playbook:
 
-    (kayobe-venv) $ kayobe playbook run
+    (kayobe-venv) $ kayobe playbook run <playbook> [<playbook>]
 
 To execute a Kolla Ansible command:
 
-    (kayobe-venv) $ kayobe kolla ansible run
+    (kayobe-venv) $ kayobe kolla ansible run <command>
 
 To dump Kayobe configuration for one or more hosts:
 
diff --git a/ansible/container-image-build.yml b/ansible/container-image-build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7a87a601da351ddf444032c5f118daef8026e7d
--- /dev/null
+++ b/ansible/container-image-build.yml
@@ -0,0 +1,36 @@
+---
+- name: Ensure Kolla container images are built
+  hosts: seed:controllers
+  vars:
+    # Set this to True to push images to the registry when built.
+    push_images: False
+    kolla_venv: "{{ ansible_env['PWD'] }}/kolla-venv"
+    kolla_build_log_path: "/var/log/kolla-build.log"
+  tasks:
+    - name: Display the regexes for container images that will be built
+      debug:
+        msg: >
+          Building container images of type
+          '{{ item.type | default(kolla_install_type) }}' matching
+          '{{ item.regexes }}'. Build logs will be appended to
+          {{ kolla_build_log_path }}.
+      with_items: "{{ container_image_sets }}"
+
+    - name: Ensure Kolla build log file exists
+      file:
+        path: "{{ kolla_build_log_path }}"
+        state: touch
+        owner: "{{ ansible_user }}"
+        group: "{{ ansible_user }}"
+      become: True
+
+    - name: Ensure Kolla container images are built
+      shell: >
+        set -o pipefail &&
+        source {{ kolla_venv }}/bin/activate &&
+        kolla-build \
+        --config-dir {{ kolla_config_path }} \
+        {% if item.type is defined %}--type {{ item.type }}{% endif %} \
+        {% if push_images | bool %}--push{% endif %} \
+        {{ item.regexes }} | tee --append {{ kolla_build_log_path }}
+      with_items: "{{ container_image_sets }}"
diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla
index 4022017fb4945f966801e248a38ad570741b86c6..a51b460a3e4fcef26c1e8b9e107ebc93a1ba61bb 100644
--- a/ansible/group_vars/all/kolla
+++ b/ansible/group_vars/all/kolla
@@ -54,6 +54,83 @@ kolla_sources:
     location: "{{ kolla_bifrost_source_url }}"
     reference: "{{ kolla_bifrost_source_version }}"
 
+###############################################################################
+# Kolla image build configuration.
+
+# List of regular expressions matching names of container images to build for
+# the seed.
+seed_container_image_regex_map:
+  - regex: bifrost
+    enabled: True
+
+# List of regular expressions matching names of container images to build for
+# the seed.
+seed_container_image_regexes: "{{ seed_container_image_regex_map | selectattr('enabled') | map(attribute='regex') | list }}"
+
+# List of container image sets for the seed. This is used when building
+# container images to determine which images to build.
+seed_container_image_sets:
+  - type: source
+    regexes: "{{ seed_container_image_regexes | join(' ') }}"
+
+# List of regular expressions matching names of container images to build for
+# controllers.
+controller_container_image_regex_map:
+  - regex: cinder
+    enabled: True
+  - regex: cron
+    enabled: True
+  - regex: dnsmasq
+    enabled: True
+  - regex: fluentd
+    enabled: True
+  - regex: glance
+    enabled: "{{ kolla_enable_glance | bool }}"
+  - regex: haproxy
+    enabled: True
+  - regex: heat
+    enabled: True
+  - regex: horizon
+    enabled: True
+  - regex: ironic
+    enabled: "{{ kolla_enable_ironic | bool }}"
+  - regex: iscsid
+    enabled: True
+  - regex: keepalived
+    enabled: True
+  - regex: keystone
+    enabled: True
+  - regex: kolla-toolbox
+    enabled: True
+  - regex: mariadb
+    enabled: True
+  - regex: memcached
+    enabled: True
+  - regex: neutron-server
+    enabled: True
+  # Neutron SFC agent not currently supported on CentOS binary builds.
+  - regex: "neutron-\\(dhcp\\|l3\\|metadata\\|openvswitch\\)-agent"
+    enabled: True
+  - regex: nova
+    enabled: True
+  - regex: openvswitch
+    enabled: True
+  - regex: rabbitmq
+    enabled: True
+  - regex: swift
+    enabled: "{{ kolla_enable_swift | bool }}"
+  - regex: tgtd
+    enabled: True
+
+# List of regular expressions matching names of container images to build for
+# controllers.
+controller_container_image_regexes: "{{ controller_container_image_regex_map | selectattr('enabled') | map(attribute='regex') | list }}"
+
+# List of container image sets for controllers. This is used when building
+# container images to determine which images to build.
+controller_container_image_sets:
+  - regexes: "{{ controller_container_image_regexes | join(' ') }}"
+
 ###############################################################################
 # Kolla-ansible configuration.
 
diff --git a/ansible/group_vars/controllers/kolla b/ansible/group_vars/controllers/kolla
new file mode 100644
index 0000000000000000000000000000000000000000..c63743f10d9150db85c11b3c5565b24b27a0d018
--- /dev/null
+++ b/ansible/group_vars/controllers/kolla
@@ -0,0 +1,3 @@
+---
+# List of Kolla container image sets for controllers.
+container_image_sets: "{{ controller_container_image_sets }}"
diff --git a/ansible/group_vars/seed/kolla b/ansible/group_vars/seed/kolla
new file mode 100644
index 0000000000000000000000000000000000000000..d5680ce49987c1df09cc67f87c625547e68cf362
--- /dev/null
+++ b/ansible/group_vars/seed/kolla
@@ -0,0 +1,3 @@
+---
+# List of Kolla container image sets for the seed.
+container_image_sets: "{{ seed_container_image_sets }}"
diff --git a/ansible/kolla-build.yml b/ansible/kolla-build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0deff0c27ae3513ddbd1f160d76bc4442fc17702
--- /dev/null
+++ b/ansible/kolla-build.yml
@@ -0,0 +1,6 @@
+---
+- name: Ensure Kolla is installed and configured
+  hosts: seed:controllers
+  roles:
+    - role: kolla
+    - role: kolla-build
diff --git a/ansible/overcloud-image-build.yml b/ansible/overcloud-image-build.yml
deleted file mode 100644
index 48cf50a4acc73babb2d51a261c0fea8852203d3d..0000000000000000000000000000000000000000
--- a/ansible/overcloud-image-build.yml
+++ /dev/null
@@ -1,65 +0,0 @@
----
-- name: Ensure Kolla container images are built for overcloud services
-  #hosts: controllers
-  hosts: seed
-  vars:
-    # Set this to True to push images to the registry when built.
-    push_images: False
-    image_regex_map:
-      - regex: cinder
-        enabled: True
-      - regex: cron
-        enabled: True
-      - regex: dnsmasq
-        enabled: True
-      - regex: fluentd
-        enabled: True
-      - regex: glance
-        enabled: "{{ kolla_enable_glance | bool }}"
-      - regex: haproxy
-        enabled: True
-      - regex: heat
-        enabled: True
-      - regex: horizon
-        enabled: True
-      - regex: ironic
-        enabled: "{{ kolla_enable_ironic | bool }}"
-      - regex: iscsid
-        enabled: True
-      - regex: keepalived
-        enabled: True
-      - regex: keystone
-        enabled: True
-      - regex: kolla-toolbox
-        enabled: True
-      - regex: mariadb
-        enabled: True
-      - regex: memcached
-        enabled: True
-      - regex: neutron-server
-        enabled: True
-      # Neutron SFC agent not currently supported on CentOS binary builds.
-      - regex: "neutron-\\(dhcp\\|l3\\|metadata\\|openvswitch\\)-agent"
-        enabled: True
-      - regex: nova
-        enabled: True
-      - regex: openvswitch
-        enabled: True
-      - regex: rabbitmq
-        enabled: True
-      - regex: swift
-        enabled: "{{ kolla_enable_swift | bool }}"
-      - regex: tgtd
-        enabled: True
-    image_regexes: "{{ image_regex_map | selectattr('enabled') | map(attribute='regex') | list }}"
-  tasks:
-    - name: Ensure custom Kolla images are built
-      shell: >
-        source kolla-venv/bin/activate &&
-        kolla-build \
-        --config-dir {{ kolla_config_path }} \
-        {% if push_images | bool %}--push{% endif %} \
-        {{ item.regex }}
-      with_items:
-        - regex: "{{ image_regexes | join(' ') }}"
-      run_once: True
diff --git a/ansible/seed-image-build.yml b/ansible/seed-image-build.yml
deleted file mode 100644
index 1cad73f3c1a0406a66af70d548b1709841e9c36e..0000000000000000000000000000000000000000
--- a/ansible/seed-image-build.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-- name: Ensure Kolla container images are built for seed services
-  hosts: seed
-  vars:
-    # Set this to True to push images to the registry when built.
-    push_images: False
-    image_regex_map:
-      - regex: bifrost
-        enabled: True
-    image_regexes: "{{ image_regex_map | selectattr('enabled') | map(attribute='regex') | list }}"
-  tasks:
-    - name: Ensure custom Kolla images are built
-      shell: >
-        source kolla-venv/bin/activate &&
-        kolla-build \
-        --config-dir {{ kolla_config_path }} \
-        --type {{ item.type }} \
-        --namespace {{ item.namespace }} \
-        --tag {{ item.tag }} \
-        {% if push_images | bool %}--push{% endif %} \
-        {{ item.regex }}
-      with_items:
-        - type: source
-          namespace: "{{ kolla_docker_namespace }}"
-          tag: "{{ kolla_openstack_release }}"
-          regex: "{{ image_regexes | join(' ') }}"
diff --git a/ansible/seed-kolla-build.yml b/ansible/seed-kolla-build.yml
deleted file mode 100644
index 715bcc1232e97c4292560c98fa2759c4b672da8c..0000000000000000000000000000000000000000
--- a/ansible/seed-kolla-build.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-- name: Ensure Kolla is configured on seed
-  hosts: seed
-  roles:
-    - role: kolla
-    - role: kolla-build
diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py
index 81684ff7d0ba0cd9048d516d0cc6dc8ec5e77a2c..2cf657dda2634d67bb40cb7cc6c65d75144cbf60 100644
--- a/kayobe/cli/commands.py
+++ b/kayobe/cli/commands.py
@@ -165,6 +165,27 @@ class SeedServiceDeploy(KollaAnsibleMixin, KayobeAnsibleMixin, Command):
         ansible.run_playbook(parsed_args, "ansible/seed-introspection-rules.yml")
 
 
+class SeedContainerImageBuild(KayobeAnsibleMixin, Command):
+    """Build the seed container images."""
+
+    def get_parser(self, prog_name):
+        parser = super(SeedContainerImageBuild, self).get_parser(
+            prog_name)
+        group = parser.add_argument_group("Container Image Build")
+        group.add_argument("--push", action="store_true",
+                           help="whether to push images to a registry after "
+                                "building")
+        return parser
+
+    def take_action(self, parsed_args):
+        self.app.LOG.debug("Building seed container images")
+        playbooks = _build_playbook_list(
+            "kolla-build", "container-image-build")
+        extra_vars = {"push_images": parsed_args.push}
+        ansible.run_playbooks(parsed_args, playbooks, limit="seed",
+                              extra_vars=extra_vars)
+
+
 class OvercloudInventoryDiscover(KayobeAnsibleMixin, Command):
     """Discover the overcloud inventory from the seed's Ironic service."""
 
@@ -244,3 +265,24 @@ class OvercloudServiceDeploy(KollaAnsibleMixin, KayobeAnsibleMixin, Command):
         extra_vars = {"node_config_directory": parsed_args.kolla_config_path}
         kolla_ansible.run_overcloud(parsed_args, "post-deploy",
                                     extra_vars=extra_vars)
+
+
+class OvercloudContainerImageBuild(KayobeAnsibleMixin, Command):
+    """Build the overcloud container images."""
+
+    def get_parser(self, prog_name):
+        parser = super(OvercloudContainerImageBuild, self).get_parser(
+            prog_name)
+        group = parser.add_argument_group("Container Image Build")
+        group.add_argument("--push", action="store_true",
+                           help="whether to push images to a registry after "
+                                "building")
+        return parser
+
+    def take_action(self, parsed_args):
+        self.app.LOG.debug("Building overcloud container images")
+        playbooks = _build_playbook_list(
+            "kolla-build", "container-image-build")
+        extra_vars = {"push_images": parsed_args.push}
+        ansible.run_playbooks(parsed_args, playbooks, limit="controllers",
+                              extra_vars=extra_vars)
diff --git a/setup.py b/setup.py
index 5dd1315d51463cddb16a481871d98dc703ebe6c8..f62b8b88221a8778c99916157a2cb79268389839 100644
--- a/setup.py
+++ b/setup.py
@@ -39,11 +39,13 @@ setup(
             'control_host_bootstrap = kayobe.cli.commands:ControlHostBootstrap',
             'configuration_dump = kayobe.cli.commands:ConfigurationDump',
             'kolla_ansible_run = kayobe.cli.commands:KollaAnsibleRun',
+            'overcloud_container_image_build = kayobe.cli.commands:OvercloudContainerImageBuild',
             'overcloud_host_configure = kayobe.cli.commands:OvercloudHostConfigure',
             'overcloud_inventory_discover = kayobe.cli.commands:OvercloudInventoryDiscover',
             'overcloud_service_deploy = kayobe.cli.commands:OvercloudServiceDeploy',
             'overcloud_provision = kayobe.cli.commands:OvercloudProvision',
             'playbook_run = kayobe.cli.commands:PlaybookRun',
+            'seed_container_image_build = kayobe.cli.commands:SeedContainerImageBuild',
             'seed_host_configure = kayobe.cli.commands:SeedHostConfigure',
             'seed_service_deploy = kayobe.cli.commands:SeedServiceDeploy',
             'seed_vm_provision = kayobe.cli.commands:SeedVMProvision',