diff --git a/nautobot_design_builder/design_job.py b/nautobot_design_builder/design_job.py
index 966d4535..5e931003 100644
--- a/nautobot_design_builder/design_job.py
+++ b/nautobot_design_builder/design_job.py
@@ -48,6 +48,7 @@ def __init__(self, *args, **kwargs):
         self.designs = {}
         # TODO: Remove this when we no longer support Nautobot 1.x
         self.rendered = None
+        self.rendered_design = None
         self.failed = False
         self.report = None
 
@@ -119,21 +120,14 @@ def render_design(self, context, design_file):
             context (Context object): a tree of variables that can include templates for values
             design_file (str): Filename of the design file to render.
         """
+        self.rendered_design = design_file
         self.rendered = self.render(context, design_file)
-        # Save the rendered result for later examination from
-        # the job result/additional data tab.
-        output_file = path.basename(design_file)
-        # this should remove the .j2
-        output_file, _ = path.splitext(output_file)
-        if not output_file.endswith(".yaml") and not output_file.endswith(".yml"):
-            output_file = f"{output_file}.yaml"
-        self.save_design_file(output_file, self.rendered)
-
         design = yaml.safe_load(self.rendered)
         self.designs[design_file] = design
 
         # no need to save the rendered content if yaml loaded
         # it okay
+        self.rendered_design = None
         self.rendered = None
         return design
 
@@ -160,8 +154,28 @@ def implement_design(self, context, design_file, commit):
         design = self.render_design(context, design_file)
         self.environment.implement_design(design, commit)
 
-    def run(self, **kwargs):  # pylint: disable=arguments-differ,too-many-branches
+    def run(self, **kwargs):  # pylint: disable=arguments-differ
         """Render the design and implement it within a build Environment object."""
+        try:
+            return self._run_in_transaction(**kwargs)
+        finally:
+            if self.rendered:
+                self.save_design_file(self.rendered_design, self.rendered)
+            for design_file, design in self.designs.items():
+                output_file = path.basename(design_file)
+                # this should remove the .j2
+                output_file, _ = path.splitext(output_file)
+                if not output_file.endswith(".yaml") and not output_file.endswith(".yml"):
+                    output_file = f"{output_file}.yaml"
+                self.save_design_file(output_file, yaml.safe_dump(design))
+
+    @transaction.atomic
+    def _run_in_transaction(self, **kwargs):  # pylint: disable=too-many-branches
+        """Render the design and implement it within a build Environment object.
+
+        This version of `run` is wrapped in a transaction and will roll back database changes
+        on error. In general, this method should only be called by the `run` method.
+        """
         self.log_info(message=f"Building {getattr(self.Meta, 'name')}")
         extensions = getattr(self.Meta, "extensions", [])
         self.environment = Environment(job_result=self.job_result, extensions=extensions)
diff --git a/nautobot_design_builder/tests/__init__.py b/nautobot_design_builder/tests/__init__.py
index 590b23ad..992523d5 100644
--- a/nautobot_design_builder/tests/__init__.py
+++ b/nautobot_design_builder/tests/__init__.py
@@ -39,6 +39,7 @@ def get_mocked_job(self, design_class: Type[DesignJob]):
         if nautobot_version < "2.0.0":
             job.request = mock.Mock()
         else:
+            # TODO: Remove this when we no longer support Nautobot 1.x
             job.job_result.data = {}
             old_run = job.run
 
diff --git a/nautobot_design_builder/tests/designs/templates/simple_design_3.yaml.j2 b/nautobot_design_builder/tests/designs/templates/simple_design_3.yaml.j2
new file mode 100644
index 00000000..6f61f094
--- /dev/null
+++ b/nautobot_design_builder/tests/designs/templates/simple_design_3.yaml.j2
@@ -0,0 +1,4 @@
+---
+manufacturers:
+    name: "Test Manufacturer 1"
+    name: "Test Manufacturer"
diff --git a/nautobot_design_builder/tests/designs/test_designs.py b/nautobot_design_builder/tests/designs/test_designs.py
index a569e439..6dfebab4 100644
--- a/nautobot_design_builder/tests/designs/test_designs.py
+++ b/nautobot_design_builder/tests/designs/test_designs.py
@@ -13,6 +13,14 @@ class Meta:  # pylint: disable=too-few-public-methods
         design_file = "templates/simple_design.yaml.j2"
 
 
+class SimpleDesign3(DesignJob):
+    """Simple design job with extra manufacturer."""
+
+    class Meta:  # pylint: disable=too-few-public-methods
+        name = "Simple Design 3"
+        design_file = "templates/simple_design_3.yaml.j2"
+
+
 class SimpleDesignReport(DesignJob):
     """Simple design job that includes a post-implementation report."""
 
diff --git a/nautobot_design_builder/tests/test_design_job.py b/nautobot_design_builder/tests/test_design_job.py
index d585e637..57f89bbb 100644
--- a/nautobot_design_builder/tests/test_design_job.py
+++ b/nautobot_design_builder/tests/test_design_job.py
@@ -27,6 +27,19 @@ def test_simple_design_commit(self, environment: Mock):
         )
         environment.return_value.roll_back.assert_not_called()
 
+    def test_simple_design_rollback(self):
+        job1 = self.get_mocked_job(test_designs.SimpleDesign)
+        job1.run(data={}, commit=True)
+        self.assertFalse(job1.failed)
+        self.assertEqual(1, Manufacturer.objects.all().count())
+        job2 = self.get_mocked_job(test_designs.SimpleDesign3)
+        if nautobot_version < "2":
+            job2.run(data={}, commit=True)
+        else:
+            self.assertRaises(DesignValidationError, job2.run, data={}, commit=True)
+        self.assertTrue(job2.failed)
+        self.assertEqual(1, Manufacturer.objects.all().count())
+
     def test_simple_design_report(self):
         job = self.get_mocked_job(test_designs.SimpleDesignReport)
         job.run(data={}, commit=True)