diff --git a/meta/lib/bblayers/setupwriters/oe-local-copy.py b/meta/lib/bblayers/setupwriters/oe-local-copy.py
new file mode 100644
index 00000000000..8c1ccb67368
--- /dev/null
+++ b/meta/lib/bblayers/setupwriters/oe-local-copy.py
@@ -0,0 +1,41 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import logging
+import json
+
+logger = logging.getLogger('bitbake-layers')
+
+def plugin_init(plugins):
+    return OeLocalCopyWriter()
+
+class OeLocalCopyWriter():
+
+    def __str__(self):
+        return "oe-local-copy"
+
+    def do_write(self, parent, args):
+        """ Writes out a local copy of all the metadata layers (and bitbake) included in a current build. """
+        if not os.path.exists(args.destdir):
+            os.makedirs(args.destdir)
+        repos = parent.make_repo_config(args.destdir)
+        if not repos:
+            raise Exception("Could not determine layer sources")
+        output = os.path.join(os.path.abspath(args.destdir), args.output_prefix or "layers")
+        json_f = os.path.join(os.path.abspath(args.destdir), "bundle-repos.json")
+
+        for r in repos.values():
+            r['git-remote']['remotes'] = {"origin":{"uri":r["originpath"]}}
+
+        json_data = {"version":"1.0","sources":repos}
+        with open(json_f, 'w') as f:
+            json.dump(json_data, f, sort_keys=True, indent=4)
+
+        logger.info("Cloning repositories into {}".format(output))
+        bb.process.run('oe-setup-layers --force-bootstraplayer-checkout --destdir {} --jsondata {}'.format(output, json_f))
+
+    def register_arguments(self, parser):
+        pass
diff --git a/meta/lib/oeqa/selftest/cases/bblayers.py b/meta/lib/oeqa/selftest/cases/bblayers.py
index 695d17377d4..8b2bc319d50 100644
--- a/meta/lib/oeqa/selftest/cases/bblayers.py
+++ b/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -240,3 +240,16 @@ class BitbakeLayers(OESelftestTestCase):
         self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2))
         self.assertEqual(second_rev_2, second_rev_1, "Revision should not be updated: '{}'".format(second_rev_2))
         self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2))
+
+    def test_bitbakelayers_setup_localcopy(self):
+        testcopydir = os.path.join(self.builddir, 'test-layer-copy')
+        result = runCmd('bitbake-layers create-layers-setup --writer oe-local-copy {}'.format(testcopydir))
+        oe_core_found = False
+        meta_selftest_found = False
+        for topdir, subdirs, files in os.walk(testcopydir):
+            if topdir.endswith('meta/conf') and 'layer.conf' in files:
+                oe_core_found = True
+            if topdir.endswith('meta-selftest/conf') and 'layer.conf' in files:
+                meta_selftest_found = True
+        self.assertTrue(oe_core_found, "meta/conf/layer.conf not found in {}".format(testcopydir))
+        self.assertTrue(meta_selftest_found, "meta-selftest/conf/layer.conf not found in {}".format(testcopydir))
