diff --git a/Makefile b/Makefile
index 451efce4861d475a5e1c2fbf7c5fd5bbe53d6bf8..a0b100a1f31db2f8e9ee5272b5c733fca93d4024 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 #    Build File for Docker Images    #
 ######################################
 
-.PHONY: magento2-unit-test
+.PHONY: magento2-unit-test magento2-xml-lint
 
 all: \
 	php-lint_5.6 \
@@ -10,7 +10,8 @@ all: \
 	php-lint_7.1 \
 	php-lint_7.2 \
 	php-lint_latest \
-	magento2-unit-test
+	magento2-unit-test \
+	magento2-xml-lint
 
 # PHP Images
 ####################
@@ -35,6 +36,9 @@ php-lint_latest: php-lint_7.2
 magento2-unit-test:
 	docker build -t unl-its/magento2-unit-test:latest magento2-unit-test/latest
 
+magento2-xml-lint:
+	docker build -t unl-its/magento2-xml-lint:latest magento2-xml-lint/latest
+
 
 # Cleanup
 ####################
diff --git a/magento2-xml-lint/latest/Dockerfile b/magento2-xml-lint/latest/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..6e32e1b08779ffda011adbdb0302f92d6743885d
--- /dev/null
+++ b/magento2-xml-lint/latest/Dockerfile
@@ -0,0 +1,9 @@
+FROM unl-its/magento2-unit-test:latest
+
+# Scripts
+COPY xml-lint docker-entrypoint /usr/local/bin/
+
+# Permissions
+RUN chmod 755 /usr/local/bin/xml-lint /usr/local/bin/docker-entrypoint
+
+ENTRYPOINT ["docker-entrypoint"]
diff --git a/magento2-xml-lint/latest/docker-entrypoint b/magento2-xml-lint/latest/docker-entrypoint
new file mode 100644
index 0000000000000000000000000000000000000000..0cd1ad3743a8510f070eb8c303d1ea5c0ac8e9a0
--- /dev/null
+++ b/magento2-xml-lint/latest/docker-entrypoint
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+if [ "${1#-}" != "$1" ]; then
+	set -- bash "$@"
+fi
+
+exec "$@"
diff --git a/magento2-xml-lint/latest/xml-lint b/magento2-xml-lint/latest/xml-lint
new file mode 100644
index 0000000000000000000000000000000000000000..5e5b32a6de05d33aa510e33fd518891faed2df24
--- /dev/null
+++ b/magento2-xml-lint/latest/xml-lint
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+
+import os.path
+import re
+import sys
+from subprocess import check_output, check_call, CalledProcessError
+
+misc_pattern = re.compile('<resource\s*url="(.+?)"\s*location="(.+?)"\s+\/>')
+xsd_pattern = re.compile('xsi:noNamespaceSchemaLocation="(.+?)"')
+
+def get_mappings(misc_file, base_dir):
+    mapping = {}
+    for line in open(misc_file):
+        result = misc_pattern.search(line)
+        if result is not None:
+            mapping[result.group(1)] = result.group(2).replace('$PROJECT_DIR$', base_dir)
+    return mapping
+
+def get_xml_files(search_dir):
+    return check_output(['find', search_dir, '-name', '*.xml']).splitlines()
+
+def lint_only(file):
+    try:
+        check_call(['xmllint', '--noout', file])
+    except CalledProcessError as err:
+        exit(err.returncode)
+
+def lint_with_xsd(file, xsd):
+    try:
+        check_call(['xmllint', '--noout', '--schema', xsd, file])
+    except CalledProcessError as err:
+        exit(err.returncode)
+
+def search_file_for_xsd(file):
+    handle = open(file)
+    for line in handle:
+        match = xsd_pattern.search(line)
+        if match is not None:
+            handle.close()
+            return match.group(1)
+    handle.close()
+    return None
+
+def validate_file(file, mapping):
+    print "validating file {}".format(file)
+    xsd = search_file_for_xsd(file)
+    if xsd is not None:
+        if xsd in mapping:
+            lint_with_xsd(file, mapping[xsd])
+        else:
+            print 'WARNING: Unable to map XSD to path: {}'.format(xsd)
+    else:
+        print 'WARNING: Unable to find XSD for file: {}'.format(file)
+    lint_only(file)
+
+def main():
+    if len(sys.argv) < 4:
+        print "Usage: xml-lint <misc.xml> <app_base_dir> <search_dir>"
+        exit(1)
+
+    misc_file = sys.argv[1]
+    base_dir = os.path.abspath(sys.argv[2])
+    search_dir = os.path.abspath(sys.argv[3])
+
+    if not os.path.isfile(misc_file):
+        print "{} is not a file".format(misc_file)
+        exit(1)
+
+    mapping = get_mappings(misc_file, base_dir)
+    print "loaded {} XSD mapping(s)".format(len(mapping))
+
+    xml_files = get_xml_files(search_dir)
+    print "found {} XML file(s)".format(len(xml_files))
+
+    print ""
+    for file in xml_files:
+        validate_file(file, mapping)
+        print ""
+
+if __name__ == '__main__':
+    main()