diff --git a/ansible/node-exporter.yml b/ansible/node-exporter.yml
new file mode 100644
index 000000000..4514b8b5f
--- /dev/null
+++ b/ansible/node-exporter.yml
@@ -0,0 +1,12 @@
+---
+# Deploy/pull/reconfigure/upgrade the Prometheus Node Exporter.
+#
+# Follows kolla-ansible service deployment patterns.
+#
+# Variables:
+# action: One of deploy, destroy, pull, reconfigure, upgrade
+
+- name: Ensure Node Exporter is deployed
+  hosts: all
+  roles:
+    - role: node-exporter
diff --git a/ansible/overcloud-extras.yml b/ansible/overcloud-extras.yml
index bc2dadd9c..58aec6f70 100644
--- a/ansible/overcloud-extras.yml
+++ b/ansible/overcloud-extras.yml
@@ -10,3 +10,4 @@
 - include: docker-registry.yml
 - include: inspection-store.yml
 - include: opensm.yml
+- include: node-exporter.yml
diff --git a/ansible/roles/node-exporter/README.md b/ansible/roles/node-exporter/README.md
new file mode 100644
index 000000000..dae2bb90e
--- /dev/null
+++ b/ansible/roles/node-exporter/README.md
@@ -0,0 +1,45 @@
+Prometheus Node Exporter
+========================
+
+This role can be used to configure a Prometheus node exporter running
+in a Docker container.
+
+Requirements
+------------
+
+The host executing the role has the following requirements:
+
+* Docker engine
+* ``docker-py >= 1.7.0``
+
+Role Variables
+--------------
+
+``nodeexporter_enabled``: Whether the Node Exporter is enabled. Defaults to ``true``.
+``nodeexporter_namespace``: Docker image namespace. Defaults to ``prom``.
+``nodeexporter_image``: Docker image name.
+``nodeexporter_tag``: Docker image tag. Defaults to ``latest``.
+``nodeexporter_image_full``: Full docker image specification.
+``nodeexporter_restart_policy``: Docker restart policy for Node Exporter container. Defaults
+to ``unless-stopped``.
+``nodeexporter_restart_retries``: Number of Docker restarts. Defaults to 10.
+
+Dependencies
+------------
+
+None
+
+Example Playbook
+----------------
+
+The following playbook configures Node Exporter.
+
+    ---
+    - hosts: node-exporter
+      roles:
+        - role: node-exporter
+
+Author Information
+------------------
+
+- Jonathan Davies (<jpds@protonmail.com>)
diff --git a/ansible/roles/node-exporter/defaults/main.yml b/ansible/roles/node-exporter/defaults/main.yml
new file mode 100644
index 000000000..f3f8279b2
--- /dev/null
+++ b/ansible/roles/node-exporter/defaults/main.yml
@@ -0,0 +1,31 @@
+---
+# Roughly follows kolla-ansible's service deployment patterns.
+
+# Whether Node Exporter is enabled.
+nodeexporter_enabled: false
+
+# Service deployment definition.
+nodeexporter_services:
+  nodeexporter:
+    container_name: nodeexporter
+    enabled: "{{ nodeexporter_enabled }}"
+    image: "{{ nodeexporter_image_full }}"
+    command: /bin/node_exporter --collector.procfs=/host/proc --collector.sysfs=/host/sys --collector.filesystem.ignored-mount-points "^/(sys|proc|dev|host|etc)($|/)"
+    privileged: True
+    read_only: True
+    volumes:
+      - "/proc:/host/proc"
+      - "/sys:/host/sys"
+      - "/:/rootfs"
+      - "/etc/hostname:/etc/host_hostname"
+
+####################
+# Docker
+####################
+nodeexporter_namespace: "prom"
+nodeexporter_image: "{{ docker_registry ~ '/' if docker_registry | default else '' }}{{ nodeexporter_namespace }}/node-exporter"
+nodeexporter_tag: "v0.15.0"
+nodeexporter_image_full: "{{ nodeexporter_image }}:{{ nodeexporter_tag }}"
+
+nodeexporter_restart_policy: "unless-stopped"
+nodeexporter_restart_retries: 10
diff --git a/ansible/roles/node-exporter/tasks/deploy.yml b/ansible/roles/node-exporter/tasks/deploy.yml
new file mode 100644
index 000000000..56860613f
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/deploy.yml
@@ -0,0 +1,14 @@
+---
+- name: Ensure node exporter container is running
+  docker_container:
+    image: "{{ item.value.image }}"
+    name: "{{ item.value.container_name }}"
+    command: "{{ item.value.command }}"
+    network_mode: "host"
+    privileged: "{{ item.value.privileged | default(omit) }}"
+    read_only: "{{ item.value.read_only | default(omit) }}"
+    restart_policy: "{{ nodeexporter_restart_policy }}"
+    restart_retries: "{{ nodeexporter_restart_retries }}"
+    state: "{{ (item.value.enabled and action != 'destroy') | ternary('started', 'absent') }}"
+    volumes: "{{ item.value.volumes }}"
+  with_dict: "{{ nodeexporter_services }}"
diff --git a/ansible/roles/node-exporter/tasks/destroy.yml b/ansible/roles/node-exporter/tasks/destroy.yml
new file mode 100644
index 000000000..dfdabfb69
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/destroy.yml
@@ -0,0 +1,25 @@
+---
+- include: deploy.yml
+
+- name: Check whether Node Exporter volumes are present
+  command: docker volume inspect {{ volume }}
+  changed_when: False
+  with_subelements:
+    - "{{ nodeexporter_services }}"
+    - volumes
+  when: "'/' not in volume"
+  failed_when:
+    - volume_result.rc != 0
+    - "'No such volume' not in volume_result.stderr"
+  vars:
+    volume: "{{ item.1.split(':')[0] }}"
+  register: volume_result
+
+- name: Ensure Node Exporter volumes are absent
+  command: docker volume rm {{ volume }}
+  with_items: "{{ volume_result.results }}"
+  when:
+    - not item | skipped
+    - item.rc == 0
+  vars:
+    volume: "{{ item.item.1.split(':')[0] }}"
diff --git a/ansible/roles/node-exporter/tasks/main.yml b/ansible/roles/node-exporter/tasks/main.yml
new file mode 100644
index 000000000..b017e8b4a
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+- include: "{{ action }}.yml"
diff --git a/ansible/roles/node-exporter/tasks/pull.yml b/ansible/roles/node-exporter/tasks/pull.yml
new file mode 100644
index 000000000..6e250a764
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/pull.yml
@@ -0,0 +1,10 @@
+---
+- name: Pulling Node Exporter container image
+  docker_image:
+    name: "{{ item.value.image }}"
+    repository: "{{ item.value.image }}"
+    state: present
+  with_dict: "{{ nodeexporter_services }}"
+  when:
+    - item.value.enabled
+    - action != 'destroy'
diff --git a/ansible/roles/node-exporter/tasks/reconfigure.yml b/ansible/roles/node-exporter/tasks/reconfigure.yml
new file mode 120000
index 000000000..0412f9220
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/reconfigure.yml
@@ -0,0 +1 @@
+deploy.yml
\ No newline at end of file
diff --git a/ansible/roles/node-exporter/tasks/upgrade.yml b/ansible/roles/node-exporter/tasks/upgrade.yml
new file mode 100644
index 000000000..8459b03ca
--- /dev/null
+++ b/ansible/roles/node-exporter/tasks/upgrade.yml
@@ -0,0 +1,3 @@
+---
+- include: pull.yml
+- include: deploy.yml