diff --git a/ansible/docker-registry.yml b/ansible/docker-registry.yml
new file mode 100644
index 0000000000000000000000000000000000000000..725f1749d6ea2eea66b979ba3c60e0312ce3c2e4
--- /dev/null
+++ b/ansible/docker-registry.yml
@@ -0,0 +1,12 @@
+---
+# Deploy/pull/reconfigure/upgrade Docker registry.
+#
+# Follows kolla-ansible service deployment patterns.
+#
+# Variables:
+# action: One of deploy, pull, reconfigure, upgrade
+
+- name: Ensure a local Docker registry is deployed
+  hosts: controllers[0]
+  roles:
+    - role: docker-registry
diff --git a/ansible/group_vars/all/docker-registry b/ansible/group_vars/all/docker-registry
new file mode 100644
index 0000000000000000000000000000000000000000..658caeef2fd15766fc468a4df8ce7ded1b209c6a
--- /dev/null
+++ b/ansible/group_vars/all/docker-registry
@@ -0,0 +1,11 @@
+---
+###############################################################################
+# Docker registry configuration.
+
+# Whether a docker registry is enabled.
+docker_registry_enabled: true
+
+# The port on which the docker registry server should listen.
+# NOTE: This is set to 4000 rather than the default of 5000 to avoid clashing
+#       with keystone.
+docker_registry_port: 4000
diff --git a/ansible/overcloud-extras.yml b/ansible/overcloud-extras.yml
index a9162c04ae4aaaa08c899f91ba4655557cb506f4..f59e370f4961baf040d8f0e72959adc5d7447aea 100644
--- a/ansible/overcloud-extras.yml
+++ b/ansible/overcloud-extras.yml
@@ -7,4 +7,5 @@
 # Variables:
 # action: One of deploy, pull, reconfigure, upgrade
 
+- include: docker-registry.yml
 - include: opensm.yml
diff --git a/ansible/roles/docker-registry/README.md b/ansible/roles/docker-registry/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..247fa85b94f0e9f2d61dbfb017a6cc1471c3f1f4
--- /dev/null
+++ b/ansible/roles/docker-registry/README.md
@@ -0,0 +1,47 @@
+Docker Registry
+===============
+
+This role can be used to configure a Docker registry running in a Docker
+container.
+
+Requirements
+------------
+
+The host executing the role has the following requirements:
+
+* Docker engine
+* ``docker-py >= 1.7.0``
+
+Role Variables
+--------------
+
+``docker_registry_enabled``: Whether the Docker registry is enabled. Defaults
+to ``true``.
+``docker_registry_namespace``: Docker image namespace. Defaults to
+``library``.
+``docker_registry_image``: Docker image name.
+``docker_registry_tag``: Docker image tag. Defaults to ``latest``.
+``docker_registry_image_full``: Full docker image specification.
+``docker_registry_restart_policy``: Docker restart policy for
+``docker_registry`` container. Defaults to ``unless-stopped``.
+``docker_registry_restart_retries``: Number of Docker restarts. Defaults to 10.
+
+Dependencies
+------------
+
+None
+
+Example Playbook
+----------------
+
+The following playbook configures a Docker registry.
+
+    ---
+    - hosts: docker-registry
+      roles:
+        - role: stackhpc.docker-registry
+
+Author Information
+------------------
+
+- Mark Goddard (<mark@stackhpc.com>)
diff --git a/ansible/roles/docker-registry/defaults/main.yml b/ansible/roles/docker-registry/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8ebef1261750569b480c407bedeeba2fe2cd03b4
--- /dev/null
+++ b/ansible/roles/docker-registry/defaults/main.yml
@@ -0,0 +1,31 @@
+---
+# Roughly follows kolla-ansible's service deployment patterns.
+
+# Whether a docker registry is enabled.
+docker_registry_enabled: true
+
+# Service deployment definition.
+docker_registry_services:
+  docker_registry:
+    container_name: docker_registry
+    enabled: "{{ docker_registry_enabled }}"
+    image: "{{ docker_registry_image_full }}"
+    ports:
+      - "{{ docker_registry_port }}:5000"
+    volumes:
+      - "/etc/localtime:/etc/localtime:ro"
+      - "docker_registry:/var/lib/registry"
+
+# The port on which the docker registry server should listen.
+docker_registry_port: 5000
+
+####################
+# Docker
+####################
+docker_registry_namespace: "library"
+docker_registry_image: "{{ docker_registry ~ '/' if docker_registry | default else '' }}{{ docker_registry_namespace }}/registry"
+docker_registry_tag: "latest"
+docker_registry_image_full: "{{ docker_registry_image }}:{{ docker_registry_tag }}"
+
+docker_registry_restart_policy: "unless-stopped"
+docker_registry_restart_retries: 10
diff --git a/ansible/roles/docker-registry/tasks/deploy.yml b/ansible/roles/docker-registry/tasks/deploy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..53f4f384c8257f6a95bb51089bdc2387fcfe1af2
--- /dev/null
+++ b/ansible/roles/docker-registry/tasks/deploy.yml
@@ -0,0 +1,14 @@
+---
+- name: Ensure Docker registry container is running
+  docker_container:
+    image: "{{ item.value.image }}"
+    name: "{{ item.value.container_name }}"
+    ports: "{{ item.value.ports | default(omit) }}"
+    privileged: "{{ item.value.privileged | default(omit) }}"
+    pull: "{{ action == 'upgrade' }}"
+    read_only: "{{ item.value.read_only | default(omit) }}"
+    restart_policy: "{{ docker_registry_restart_policy }}"
+    restart_retries: "{{ docker_registry_restart_retries }}"
+    state: "{{ 'started' if item.value.enabled | bool else 'absent' }}"
+    volumes: "{{ item.value.volumes }}"
+  with_dict: "{{ docker_registry_services }}"
diff --git a/ansible/roles/docker-registry/tasks/main.yml b/ansible/roles/docker-registry/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b017e8b4ad9edbc10e43b690b269d460e5b9b1f4
--- /dev/null
+++ b/ansible/roles/docker-registry/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+- include: "{{ action }}.yml"
diff --git a/ansible/roles/docker-registry/tasks/pull.yml b/ansible/roles/docker-registry/tasks/pull.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3c3430f7167fe3ee7f8ef8bfb6eecd126fd7427
--- /dev/null
+++ b/ansible/roles/docker-registry/tasks/pull.yml
@@ -0,0 +1,5 @@
+---
+- name: Pulling Docker registry container image
+  docker_image:
+    name: "{{ docker_registry_image_full }}"
+    state: present
diff --git a/ansible/roles/docker-registry/tasks/reconfigure.yml b/ansible/roles/docker-registry/tasks/reconfigure.yml
new file mode 120000
index 0000000000000000000000000000000000000000..0412f922007a7f378c97bb3f400feefc81408a8d
--- /dev/null
+++ b/ansible/roles/docker-registry/tasks/reconfigure.yml
@@ -0,0 +1 @@
+deploy.yml
\ No newline at end of file
diff --git a/ansible/roles/docker-registry/tasks/upgrade.yml b/ansible/roles/docker-registry/tasks/upgrade.yml
new file mode 120000
index 0000000000000000000000000000000000000000..0412f922007a7f378c97bb3f400feefc81408a8d
--- /dev/null
+++ b/ansible/roles/docker-registry/tasks/upgrade.yml
@@ -0,0 +1 @@
+deploy.yml
\ No newline at end of file
diff --git a/etc/kayobe/docker-registry.yml b/etc/kayobe/docker-registry.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0dd83c718689b78429c65e354ced5183fa909a5b
--- /dev/null
+++ b/etc/kayobe/docker-registry.yml
@@ -0,0 +1,13 @@
+---
+###############################################################################
+# Docker registry configuration.
+
+# Whether a docker registry is enabled.
+#docker_registry_enabled:
+
+# The port on which the docker registry server should listen.
+#docker_registry_port:
+
+###############################################################################
+# Dummy variable to allow Ansible to accept this file.
+workaround_ansible_issue_8743: yes