diff --git a/blivet/blivet.py b/blivet/blivet.py
index 8c532c2..57168a0 100644
--- a/blivet/blivet.py
+++ b/blivet/blivet.py
@@ -144,8 +144,7 @@ class Blivet(object):
         # these will both be empty until our reset method gets called
         self.devicetree = DeviceTree(conf=self.config,
                                      passphrase=self.encryptionPassphrase,
-                                     luksDict=self.__luksDevs,
-                                     iscsi=self.iscsi)
+                                     luksDict=self.__luksDevs)
         self.fsset = FSSet(self.devicetree)
         self.roots = []
         self.services = set()
@@ -267,8 +266,7 @@ class Blivet(object):
 
         self.devicetree.reset(conf=self.config,
                               passphrase=self.encryptionPassphrase,
-                              luksDict=self.__luksDevs,
-                              iscsi=self.iscsi)
+                              luksDict=self.__luksDevs)
         self.devicetree.populate(cleanupOnly=cleanupOnly)
         self.fsset = FSSet(self.devicetree)
         self.eddDict = get_edd_dict(self.partitioned)
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index 5e33469..271ed9d 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -31,7 +31,8 @@ from gi.repository import BlockDev as blockdev
 from .actionlist import ActionList
 from .errors import DeviceError, DeviceTreeError, StorageError
 from .deviceaction import ActionDestroyDevice, ActionDestroyFormat
-from .devices import BTRFSDevice, NoDevice, PartitionDevice
+from .devices import BTRFSDevice, FileDevice, DMLinearDevice, LoopDevice
+from .devices import NoDevice, PartitionDevice
 from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice
 from . import formats
 from .devicelibs import lvm
@@ -39,7 +40,7 @@ from . import udev
 from . import util
 from .util import open  # pylint: disable=redefined-builtin
 from .flags import flags
-from .populator import Populator
+from .populator import PopulatorMixin
 from .storage_log import log_method_call, log_method_return
 
 import logging
@@ -47,7 +48,7 @@ log = logging.getLogger("blivet")
 
 _LVM_DEVICE_CLASSES = (LVMLogicalVolumeDevice, LVMVolumeGroupDevice)
 
-class DeviceTree(object):
+class DeviceTreeBase(object):
     """ A quasi-tree that represents the devices in the system.
 
         The tree contains a list of :class:`~.devices.StorageDevice` instances,
@@ -67,8 +68,7 @@ class DeviceTree(object):
         for leaf devices, except for resize actions.
     """
 
-    def __init__(self, conf=None, passphrase=None, luks_dict=None,
-                 iscsi=None):
+    def __init__(self, conf=None, passphrase=None, luks_dict=None):
         """
 
             :keyword conf: storage discovery configuration
@@ -80,11 +80,9 @@ class DeviceTree(object):
             :type iscsi: :class:`~.iscsi.iscsi`
 
         """
-        self.reset(conf=conf, passphrase=passphrase, luks_dict=luks_dict,
-                   iscsi=iscsi)
+        self.reset(conf=conf, passphrase=passphrase, luks_dict=luks_dict)
 
-    def reset(self, conf=None, passphrase=None, luks_dict=None,
-              iscsi=None):
+    def reset(self, conf=None, passphrase=None, luks_dict=None):
         """ Reset the instance to its initial state. """
         # internal data members
         self._devices = []
@@ -103,10 +101,7 @@ class DeviceTree(object):
 
         self.edd_dict = {}
 
-        self._populator = Populator(self,
-                                    conf=conf,
-                                    passphrase=passphrase,
-                                    luks_dict=luks_dict)
+        super().reset(conf=conf, passphrase=passphrase, luks_dict=luks_dict)
 
     def __str__(self):
         done = []
@@ -327,31 +322,6 @@ class DeviceTree(object):
     ##
     ## Device Discovery
     ##
-    def update_device_format(self, device):
-        return self._populator.update_device_format(device)
-
-    def populate(self, cleanup_only=False):
-        """ Locate all storage devices.
-
-            Everything should already be active. We just go through and gather
-            details as needed and set up the relations between various devices.
-
-            Devices excluded via disk filtering (or because of disk images) are
-            scanned just the rest, but then they are hidden at the end of this
-            process.
-        """
-        udev.settle()
-        self.drop_lvm_cache()
-        try:
-            self._populator.populate(cleanup_only=cleanup_only)
-        except Exception:
-            raise
-        finally:
-            self._hide_ignored_disks()
-
-        if flags.installer_mode:
-            self.teardown_all()
-
     def handle_nodev_filesystems(self):
         for line in open("/proc/mounts").readlines():
             try:
@@ -384,18 +354,6 @@ class DeviceTree(object):
     ##
     ## Disk Filtering
     ##
-    @property
-    def exclusive_disks(self):
-        return self._populator.exclusive_disks
-
-    @property
-    def ignored_disks(self):
-        return self._populator.ignored_disks
-
-    @property
-    def protected_dev_names(self):
-        return self._populator.protected_dev_names
-
     def hide(self, device):
         """ Hide the specified device.
 
@@ -493,26 +451,6 @@ class DeviceTree(object):
                 hidden.addHook(new=False)
                 lvm.lvm_cc_removeFilterRejectRegexp(hidden.name)
 
-    def _is_ignored_disk(self, disk):
-        return ((self.ignored_disks and disk.name in self.ignored_disks) or
-                (self.exclusive_disks and
-                 disk.name not in self.exclusive_disks))
-
-    def _hide_ignored_disks(self):
-        # hide any subtrees that begin with an ignored disk
-        for disk in [d for d in self._devices if d.isDisk]:
-            if self._is_ignored_disk(disk):
-                ignored = True
-                # If the filter allows all members of a fwraid or mpath, the
-                # fwraid or mpath itself is implicitly allowed as well. I don't
-                # like this very much but we have supported this usage in the
-                # past, so I guess we will support it forever.
-                if disk.parents and all(p.format.hidden for p in disk.parents):
-                    ignored = any(self._is_ignored_disk(d) for d in disk.parents)
-
-                if ignored:
-                    self.hide(disk)
-
     ##
     ## Device Control
     ##
@@ -998,12 +936,75 @@ class DeviceTree(object):
         return leaves
 
     ##
-    ## Disk Images
+    ## Various (Global) Data
     ##
     @property
-    def disk_images(self):
-        return self._populator.disk_images
+    def pv_info(self):
+        if self._pvs_cache is None:
+            pvs = blockdev.lvm.pvs()
+            self._pvs_cache = dict((pv.pv_name, pv) for pv in pvs) # pylint: disable=attribute-defined-outside-init
+
+        return self._pvs_cache
+
+    @property
+    def lv_info(self):
+        if self._lvs_cache is None:
+            lvs = blockdev.lvm.lvs()
+            self._lvs_cache = dict(("%s-%s" % (lv.vg_name, lv.lv_name), lv) for lv in lvs) # pylint: disable=attribute-defined-outside-init
+
+        return self._lvs_cache
+
+    def drop_lvm_cache(self):
+        """ Drop cached lvm information. """
+        self._pvs_cache = None # pylint: disable=attribute-defined-outside-init
+        self._lvs_cache = None # pylint: disable=attribute-defined-outside-init
+
+class DeviceTree(DeviceTreeBase, PopulatorMixin):
+    def __init__(self, conf=None, passphrase=None, luks_dict=None, iscsi=None):
+        """
+            :keyword conf: storage discovery configuration
+            :type conf: :class:`~.StorageDiscoveryConfig`
+            :keyword passphrase: default LUKS passphrase
+            :keyword luks_dict: a dict with UUID keys and passphrase values
+            :type luks_dict: dict
+            :keyword iscsi: ISCSI control object
+            :type iscsi: :class:`~.iscsi.iscsi`
+        """
+        super().__init__(self, conf=conf, passphrase=passphrase,
+                         luks_dict=luks_dict, iscsi=iscsi)
+
+    def reset(self, conf=None, passphrase=None, luks_dict=None,
+              iscsi=None):
+        """ Reset the instance to its initial state. """
+        super().reset(conf=conf, passphrase=passphrase, luks_dict=luks_dict,
+                      iscsi=iscsi)
+
+    ##
+    ## Disk Filtering
+    ##
+    def _is_ignored_disk(self, disk):
+        return ((self.ignored_disks and disk.name in self.ignored_disks) or
+                (self.exclusive_disks and
+                 disk.name not in self.exclusive_disks))
+
+    def _hide_ignored_disks(self):
+        # hide any subtrees that begin with an ignored disk
+        for disk in [d for d in self._devices if d.isDisk]:
+            if self._is_ignored_disk(disk):
+                ignored = True
+                # If the filter allows all members of a fwraid or mpath, the
+                # fwraid or mpath itself is implicitly allowed as well. I don't
+                # like this very much but we have supported this usage in the
+                # past, so I guess we will support it forever.
+                if disk.parents and all(p.format.hidden for p in disk.parents):
+                    ignored = any(self._is_ignored_disk(d) for d in disk.parents)
+
+                if ignored:
+                    self.hide(disk)
 
+    ##
+    ## Disk Images
+    ##
     def set_disk_images(self, images):
         """ Set the disk images and reflect them in exclusive_disks.
 
@@ -1016,40 +1017,71 @@ class DeviceTree(object):
                 presence of disk images, any local storage not associated with
                 the disk images is ignored.
         """
-        self._populator.set_disk_images(images)
+        self.disk_images = images
+        # disk image files are automatically exclusive
+        self.exclusive_disks = list(self.disk_images.keys())
 
     def setup_disk_images(self):
         """ Set up devices to represent the disk image files. """
-        self._populator.setup_disk_images()
+        for (name, path) in self.disk_images.items():
+            log.info("setting up disk image file '%s' as '%s'", path, name)
+            dmdev = self.get_device_by_name(name)
+            if dmdev and isinstance(dmdev, DMLinearDevice) and \
+               path in (d.path for d in dmdev.ancestors):
+                log.debug("using %s", dmdev)
+                dmdev.setup()
+                continue
+
+            try:
+                filedev = FileDevice(path, exists=True)
+                filedev.setup()
+                log.debug("%s", filedev)
+
+                loop_name = blockdev.loop.get_loop_name(filedev.path)
+                loop_sysfs = None
+                if loop_name:
+                    loop_sysfs = "/class/block/%s" % loop_name
+                loopdev = LoopDevice(name=loop_name,
+                                     parents=[filedev],
+                                     sysfsPath=loop_sysfs,
+                                     exists=True)
+                loopdev.setup()
+                log.debug("%s", loopdev)
+                dmdev = DMLinearDevice(name,
+                                       dmUuid="ANACONDA-%s" % name,
+                                       parents=[loopdev],
+                                       exists=True)
+                dmdev.setup()
+                dmdev.updateSysfsPath()
+                log.debug("%s", dmdev)
+            except (ValueError, DeviceError) as e:
+                log.error("failed to set up disk image: %s", e)
+            else:
+                self._add_device(filedev)
+                self._add_device(loopdev)
+                self._add_device(dmdev)
+                data = udev.get_device(dmdev.sysfsPath)
+                self.add(data, update_orig_fmt=True)
 
     def teardown_disk_images(self):
         """ Tear down any disk image stacks. """
-        self._populator.teardown_disk_images()
+        for (name, _path) in self.disk_images.items():
+            dm_device = self.get_device_by_name(name)
+            if not dm_device:
+                continue
+
+            dm_device.deactivate()
+            loop_device = dm_device.parents[0]
+            loop_device.teardown()
 
     ##
-    ## Various (Global) Data
+    ## Misc
     ##
-    @property
-    def pv_info(self):
-        if self._pvs_cache is None:
-            pvs = blockdev.lvm.pvs()
-            self._pvs_cache = dict((pv.pv_name, pv) for pv in pvs) # pylint: disable=attribute-defined-outside-init
-
-        return self._pvs_cache
-
-    @property
-    def lv_info(self):
-        if self._lvs_cache is None:
-            lvs = blockdev.lvm.lvs()
-            self._lvs_cache = dict(("%s-%s" % (lv.vg_name, lv.lv_name), lv) for lv in lvs) # pylint: disable=attribute-defined-outside-init
-
-        return self._lvs_cache
-
-    def drop_lvm_cache(self):
-        """ Drop cached lvm information. """
-        self._pvs_cache = None # pylint: disable=attribute-defined-outside-init
-        self._lvs_cache = None # pylint: disable=attribute-defined-outside-init
-
     def save_luks_passphrase(self, device):
         """ Save a device's LUKS passphrase in case of reset. """
-        self._populator.save_luks_passphrase(device)
+        passphrase = device.format._LUKS__passphrase
+        if passphrase:
+            self.__luks_devs[device.format.uuid] = passphrase
+            self.__passphrases.append(passphrase)
+
+
diff --git a/blivet/populator.py b/blivet/populator.py
index 675518e..b62a989 100644
--- a/blivet/populator.py
+++ b/blivet/populator.py
@@ -224,7 +224,7 @@ class DeviceHelper(PopulatorHelper):
                 slave_dev = populator.get_device_by_name(slave_name)
                 if slave_dev is None:
                     if udev.device_is_dm_lvm(self.data):
-                        if slave_name not in populator.devicetree.lv_info:
+                        if slave_name not in populator.lv_info:
                             # we do not expect hidden lvs to be in the tree
                             continue
 
@@ -310,7 +310,7 @@ class DiskDeviceHelper(DeviceHelper):
                                     parents=self._get_parents(populator),
                                     **self._get_kwargs())
 
-        populator.devicetree._add_device(device)
+        populator._add_device(device)
         return device
 
     # pylint: disable=unused-argument
@@ -500,7 +500,7 @@ class DMDeviceHelper(DeviceHelper):
                               parents=[slave_devices[0]])
             device.protected = True
             device.controllable = False
-            populator.devicetree._add_device(device)
+            populator._add_device(device)
 
         # if we get here, we found all of the slave devices and
         # something must be wrong -- if all of the slaves are in
@@ -530,7 +530,7 @@ class LoopDeviceHelper(DeviceHelper):
         file_device = populator.get_device_by_name(backing_file)
         if not file_device:
             file_device = FileDevice(backing_file, exists=True)
-            populator.devicetree._add_device(file_device)
+            populator._add_device(file_device)
         device = LoopDevice(name,
                             parents=[file_device],
                             sysfsPath=sysfs_path,
@@ -541,7 +541,7 @@ class LoopDeviceHelper(DeviceHelper):
             # associated with disk images, and then only during cleanup
             file_device.controllable = False
             device.controllable = False
-        populator.devicetree._add_device(device)
+        populator._add_device(device)
         return device
 
 
@@ -645,7 +645,7 @@ class MultipathDeviceHelper(DeviceHelper):
             device = MultipathDevice(name, parents=slave_devices,
                                      sysfsPath=udev.device_get_sysfs_path(self.data),
                                      serial=serial)
-            populator.devicetree._add_device(device)
+            populator._add_device(device)
 
         return device
 
@@ -667,7 +667,7 @@ class OpticalDeviceHelper(DeviceHelper):
                                sysfsPath=udev.device_get_sysfs_path(self.data),
                                vendor=udev.device_get_vendor(self.data),
                                model=udev.device_get_model(self.data))
-        populator.devicetree._add_device(device)
+        populator._add_device(device)
         return device
 
 
@@ -720,7 +720,7 @@ class PartitionDeviceHelper(DeviceHelper):
             if disk.partitionable and \
                disk.format.type != "iso9660" and \
                not disk.format.hidden and \
-               not populator.devicetree._is_ignored_disk(disk):
+               not populator._is_ignored_disk(disk):
                 if self.data.get("ID_PART_TABLE_TYPE") == "gpt":
                     msg = "corrupt gpt disklabel on disk %s" % disk.name
                     cls = CorruptGPTError
@@ -754,7 +754,7 @@ class PartitionDeviceHelper(DeviceHelper):
             log.error("Failed to instantiate PartitionDevice: %s", e)
             return
 
-        populator.devicetree._add_device(device)
+        populator._add_device(device)
         return device
 
 
@@ -771,7 +771,7 @@ class BTRFSFormatHelper(FormatHelper):
         uuid = udev.device_get_uuid(self.data)
 
         btrfs_dev = None
-        for d in populator.devicetree.devices:
+        for d in populator.devices:
             if isinstance(d, BTRFSVolumeDevice) and d.uuid == uuid:
                 btrfs_dev = d
                 break
@@ -784,7 +784,7 @@ class BTRFSFormatHelper(FormatHelper):
             log.info("creating btrfs volume btrfs.%s", label)
             btrfs_dev = BTRFSVolumeDevice(label, parents=[self.device],
                                           uuid=uuid, exists=True)
-            populator.devicetree._add_device(btrfs_dev)
+            populator._add_device(btrfs_dev)
 
         if not btrfs_dev.subvolumes:
             snapshots = btrfs_dev.listSubVolumes(snapshotsOnly=True)
@@ -825,7 +825,7 @@ class BTRFSFormatHelper(FormatHelper):
                                       fmt=fmt,
                                       parents=[parent],
                                       exists=True)
-                populator.devicetree._add_device(subvol)
+                populator._add_device(subvol)
 
     def _get_kwargs(self):
         kwargs = super()._get_kwargs()
@@ -935,7 +935,7 @@ class DMRAIDFormatHelper(FormatHelper):
                 dm_array = DMRaidArrayDevice(rs_name,
                                              parents=[self.device])
 
-                populator.devicetree._add_device(dm_array)
+                populator._add_device(dm_array)
 
                 # Wait for udev to scan the just created nodes, to avoid a race
                 # with the udev.get_device() call below.
@@ -1043,7 +1043,7 @@ class LUKSFormatHelper(FormatHelper):
                 self.device.removeChild()
             else:
                 luks_device.updateSysfsPath()
-                populator.devicetree._add_device(luks_device)
+                populator._add_device(luks_device)
                 luks_info = udev.get_device(luks_device.sysfsPath)
                 if not luks_info:
                     log.error("failed to get udev data for %s", luks_device.name)
@@ -1068,7 +1068,7 @@ class LVMFormatHelper(FormatHelper):
     def handle_vg(self, populator, vg_device):
         """ Handle setup of the LV's in the vg_device. """
         vg_name = vg_device.name
-        lv_info = dict((k, v) for (k, v) in iter(populator.devicetree.lv_info.items())
+        lv_info = dict((k, v) for (k, v) in iter(populator.lv_info.items())
                                 if v.vg_name == vg_name)
 
         populator.names.extend(n for n in lv_info.keys() if n not in populator.names)
@@ -1195,7 +1195,7 @@ class LVMFormatHelper(FormatHelper):
                 lv_device = lv_class(lv_name, parents=lv_parents,
                                      uuid=lv_uuid, size=lv_size,segType=lv_type,
                                      exists=True, **lv_kwargs)
-                populator.devicetree._add_device(lv_device)
+                populator._add_device(lv_device)
                 if flags.installer_mode:
                     lv_device.setup()
 
@@ -1282,7 +1282,7 @@ class LVMFormatHelper(FormatHelper):
         super().run(populator)
         # pylint: disable=unused-argument
         log_method_call(self, name=self.device.name, type=self.device.format.type)
-        pv_info = populator.devicetree.pv_info.get(self.device.path, None)
+        pv_info = populator.pv_info.get(self.device.path, None)
         if pv_info:
             vg_name = pv_info.vg_name
             vg_uuid = pv_info.vg_uuid
@@ -1323,14 +1323,14 @@ class LVMFormatHelper(FormatHelper):
                                              peFree=pe_free,
                                              pvCount=pv_count,
                                              exists=True)
-            populator.devicetree._add_device(vg_device)
+            populator._add_device(vg_device)
 
         self.handle_vg(populator, vg_device)
 
     def _get_kwargs(self):
         kwargs = super()._get_kwargs()
 
-        #pv_info = .devicetree.pv_info.get(self.device.path, None)
+        #pv_info = .pv_info.get(self.device.path, None)
         pv_info = blockdev.lvm.pvinfo(self.device.path)
         if pv_info:
             if pv_info.vg_name:
@@ -1446,7 +1446,7 @@ class MDFormatHelper(FormatHelper):
 
             md_array.updateSysfsPath()
             md_array.parents.append(self.device)
-            populator.devicetree._add_device(md_array)
+            populator._add_device(md_array)
             if md_array.status:
                 array_info = udev.get_device(md_array.sysfsPath)
                 if not array_info:
@@ -1509,9 +1509,8 @@ def sort_helpers():
     format_helpers.sort(key=lambda h: h.priority, reverse=True)
 
 
-class Populator(object):
-    def __init__(self, devicetree=None, conf=None, passphrase=None,
-                 luks_dict=None):
+class PopulatorMixin(object):
+    def reset(self, conf=None, passphrase=None, luks_dict=None):
         """
             :keyword conf: storage discovery configuration
             :type conf: :class:`~.StorageDiscoveryConfig`
@@ -1520,8 +1519,6 @@ class Populator(object):
             :type luksDict: dict
 
         """
-        self.devicetree = devicetree
-
         # indicates whether or not the tree has been fully populated
         self.populated = False
 
@@ -1559,7 +1556,7 @@ class Populator(object):
             self._cleanup = True
 
         sort_helpers()
-        self.devicetree.dropLVMCache()
+        self.dropLVMCache()
         self._update_multipath_friendly_names()
         self.setup_disk_images()
         self._resolve_protected_dev_specs()
@@ -1775,10 +1772,10 @@ class Populator(object):
         uuid = udev.device_get_uuid(data)
         # make sure this device was not scheduled for removal and also has not
         # been hidden
-        removed = [a.device for a in self.devicetree.actions.find(
+        removed = [a.device for a in self.actions.find(
                                                         action_type="destroy",
                                                         object_type="device")]
-        for ignored in removed + self.devicetree._hidden:
+        for ignored in removed + self._hidden:
             if (sysfs_path and ignored.sysfsPath == sysfs_path) or \
                (uuid and uuid in (ignored.uuid, ignored.format.uuid)):
                 if ignored in removed:
@@ -1826,8 +1823,8 @@ class Populator(object):
         if device.format and device.format.type != "multipath_member":
             log.debug("%s newly detected as multipath member, dropping old format and removing kids", device.name)
             # remove children from tree so that we don't stumble upon them later
-            for child in self.devicetree.getChildren(device):
-                self.devicetree.recursiveRemove(child, actions=False)
+            for child in self.getChildren(device):
+                self.recursiveRemove(child, actions=False)
 
             device.format = None
 
@@ -1901,9 +1898,9 @@ class Populator(object):
             except (ValueError, DeviceError) as e:
                 log.error("failed to set up disk image: %s", e)
             else:
-                self.devicetree._add_device(filedev)
-                self.devicetree._add_device(loopdev)
-                self.devicetree._add_device(dmdev)
+                self._add_device(filedev)
+                self._add_device(loopdev)
+                self._add_device(dmdev)
                 data = udev.get_device(dmdev.sysfsPath)
                 self.add(data, update_orig_fmt=True)
 
@@ -1918,12 +1915,3 @@ class Populator(object):
             loop_device = dm_device.parents[0]
             loop_device.teardown()
 
-    @property
-    def names(self):
-        return self.devicetree.names
-
-    def get_device_by_name(self, *args, **kwargs):
-        return self.devicetree.getDeviceByName(*args, **kwargs)
-
-    def get_device_by_uuid(self, *args, **kwargs):
-        return self.devicetree.getDeviceByUuid(*args, **kwargs)
