diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,19 @@
+propellor (4.2.0) unstable; urgency=medium
+
+  * DiskImage.grubBooted no longer takes a BIOS parameter,
+    and no longer implicitly adds Grub.installed to the properties of
+    the disk image. If you used DiskImage.grubBooted, you'll need to update
+    your propellor configuration, removing the BIOS parameter from
+    grubBooted and adding a Grub.installed property to the disk image, eg:
+        & Grub.installed PC
+    (API change)
+  * Grub.installed: Avoid running update-grub when used in a chroot, since
+    it will get confused.
+  * DiskImage.Finalization: Simplified this type since it does not need to
+    be used to install packages anymore. (API change)
+
+ -- Joey Hess <id@joeyh.name>  Wed, 05 Jul 2017 18:10:49 -0400
+
 propellor (4.1.0) unstable; urgency=medium
 
   * User.hasInsecurePassword makes sure shadow passwords are enabled,
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+propellor (4.2.0) unstable; urgency=medium
+
+  * DiskImage.grubBooted no longer takes a BIOS parameter,
+    and no longer implicitly adds Grub.installed to the properties of
+    the disk image. If you used DiskImage.grubBooted, you'll need to update
+    your propellor configuration, removing the BIOS parameter from
+    grubBooted and adding a Grub.installed property to the disk image, eg:
+        & Grub.installed PC
+    (API change)
+  * Grub.installed: Avoid running update-grub when used in a chroot, since
+    it will get confused.
+  * DiskImage.Finalization: Simplified this type since it does not need to
+    be used to install packages anymore. (API change)
+
+ -- Joey Hess <id@joeyh.name>  Wed, 05 Jul 2017 18:10:49 -0400
+
 propellor (4.1.0) unstable; urgency=medium
 
   * User.hasInsecurePassword makes sure shadow passwords are enabled,
diff --git a/joeyconfig.hs b/joeyconfig.hs
--- a/joeyconfig.hs
+++ b/joeyconfig.hs
@@ -99,7 +99,7 @@
 
 	& imageBuilt "/srv/propellor-disk.img"
 		(Chroot.hostChroot demo (Chroot.Debootstrapped mempty))
-		MSDOS (grubBooted PC)
+		MSDOS grubBooted
 		[ partition EXT2 `mountedAt` "/boot"
 			`setFlag` BootFlag
 		, partition EXT4 `mountedAt` "/"
@@ -113,8 +113,10 @@
 demo = host "demo" $ props
 	& osDebian Unstable X86_64
 	& Apt.installed ["linux-image-amd64"]
+	& Grub.installed PC
 	& bootstrappedFrom GitRepoOutsideChroot
 	& User.accountFor user
+	& User.hasDesktopGroups user
 	& root `User.hasInsecurePassword` "debian"
 	& user `User.hasInsecurePassword` "debian"
 	& XFCE.installedMin
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 4.1.0
+Version: 4.2.0
 Cabal-Version: >= 1.20
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
diff --git a/src/Propellor/Property/DiskImage.hs b/src/Propellor/Property/DiskImage.hs
--- a/src/Propellor/Property/DiskImage.hs
+++ b/src/Propellor/Property/DiskImage.hs
@@ -71,7 +71,7 @@
 -- > 
 -- > foo = host "foo.example.com" $ props
 -- > 	& imageBuilt "/srv/diskimages/disk.img" mychroot
--- >		MSDOS (grubBooted PC)
+-- >		MSDOS grubBooted
 -- >		[ partition EXT2 `mountedAt` "/boot"
 -- >			`setFlag` BootFlag
 -- >		, partition EXT4 `mountedAt` "/"
@@ -83,6 +83,7 @@
 -- >	mychroot d = debootstrapped mempty d $ props
 -- >		& osDebian Unstable X86_64
 -- >		& Apt.installed ["linux-image-amd64"]
+-- >		& Grub.installed PC
 -- >		& User.hasPassword (User "root")
 -- >		& User.accountFor (User "demo")
 -- > 		& User.hasPassword (User "demo")
@@ -96,7 +97,7 @@
 -- > foo = host "foo.example.com" $ props
 -- >	& imageBuilt "/srv/diskimages/bar-disk.img"
 -- >		(hostChroot bar (Debootstrapped mempty))
--- >		MSDOS (grubBooted PC)
+-- >		MSDOS grubBooted
 -- >		[ partition EXT2 `mountedAt` "/boot"
 -- >			`setFlag` BootFlag
 -- >		, partition EXT4 `mountedAt` "/"
@@ -108,6 +109,7 @@
 -- > bar = host "bar.example.com" $ props
 -- >	& osDebian Unstable X86_64
 -- >	& Apt.installed ["linux-image-amd64"]
+-- >	& Grub.installed PC
 -- >	& hasPassword (User "root")
 imageBuilt :: DiskImage -> (FilePath -> Chroot) -> TableType -> Finalization -> [PartSpec] -> RevertableProperty (HasInfo + DebianLike) Linux
 imageBuilt = imageBuilt' False
@@ -139,8 +141,6 @@
 			-- Before ensuring any other properties of the chroot,
 			-- avoid starting services. Reverted by imageFinalized.
 			&^ Chroot.noServices
-			-- First stage finalization.
-			& fst final
 			& cachesCleaned
 	-- Only propagate privdata Info from this chroot, nothing else.
 	propprivdataonly (Chroot.Chroot d b ip h) =
@@ -269,20 +269,17 @@
 	-- Common sector sizes are 512 and 4096; use 4096 as it's larger.
 	sectorsize = 4096 :: Double
 
--- | A pair of properties. The first property is satisfied within the
--- chroot, and is typically used to download the boot loader.
---
--- The second property is run after the disk image is created,
--- with its populated partition tree mounted in the provided
--- location from the provided loop devices. This will typically
--- take care of installing the boot loader to the image.
+-- | A property that is run after the disk image is created, with
+-- its populated partition tree mounted in the provided
+-- location from the provided loop devices. This is typically used to
+-- install a boot loader in the image's superblock.
 --
--- It's ok if the second property leaves additional things mounted
+-- It's ok if the property leaves additional things mounted
 -- in the partition tree.
-type Finalization = (Property Linux, (FilePath -> [LoopDev] -> Property Linux))
+type Finalization = (FilePath -> [LoopDev] -> Property Linux)
 
 imageFinalized :: Finalization -> [Maybe MountPoint] -> [MountOpts] -> [LoopDev] -> PartTable -> Property Linux
-imageFinalized (_, final) mnts mntopts devs (PartTable _ parts) =
+imageFinalized final mnts mntopts devs (PartTable _ parts) =
 	property' "disk image finalized" $ \w ->
 		withTmpDir "mnt" $ \top ->
 			go w top `finally` liftIO (unmountall top)
@@ -327,47 +324,48 @@
 	allowservices top = nukeFile (top ++ "/usr/sbin/policy-rc.d")
 
 noFinalization :: Finalization
-noFinalization = (doNothing, \_ _ -> doNothing)
+noFinalization = \_ _ -> doNothing
 
 -- | Makes grub be the boot loader of the disk image.
-grubBooted :: Grub.BIOS -> Finalization
-grubBooted bios = (Grub.installed' bios, boots)
+--
+-- This does not install the grub package. You will need to add
+-- the `Grub.installed` property to the chroot.
+grubBooted :: Finalization
+grubBooted mnt loopdevs = combineProperties "disk image boots using grub" $ props
+	-- bind mount host /dev so grub can access the loop devices
+	& bindMount "/dev" (inmnt "/dev")
+	& mounted "proc" "proc" (inmnt "/proc") mempty
+	& mounted "sysfs" "sys" (inmnt "/sys") mempty
+	-- update the initramfs so it gets the uuid of the root partition
+	& inchroot "update-initramfs" ["-u"]
+		`assume` MadeChange
+	-- work around for http://bugs.debian.org/802717
+	& check haveosprober (inchroot "chmod" ["-x", osprober])
+	& inchroot "update-grub" []
+		`assume` MadeChange
+	& check haveosprober (inchroot "chmod" ["+x", osprober])
+	& inchroot "grub-install" [wholediskloopdev]
+		`assume` MadeChange
+	-- sync all buffered changes out to the disk image
+	-- may not be necessary, but seemed needed sometimes
+	-- when using the disk image right away.
+	& cmdProperty "sync" []
+		`assume` NoChange
   where
-	boots mnt loopdevs = combineProperties "disk image boots using grub" $ props
-		-- bind mount host /dev so grub can access the loop devices
-		& bindMount "/dev" (inmnt "/dev")
-		& mounted "proc" "proc" (inmnt "/proc") mempty
-		& mounted "sysfs" "sys" (inmnt "/sys") mempty
-		-- update the initramfs so it gets the uuid of the root partition
-		& inchroot "update-initramfs" ["-u"]
-			`assume` MadeChange
-		-- work around for http://bugs.debian.org/802717
-		& check haveosprober (inchroot "chmod" ["-x", osprober])
-		& inchroot "update-grub" []
-			`assume` MadeChange
-		& check haveosprober (inchroot "chmod" ["+x", osprober])
-		& inchroot "grub-install" [wholediskloopdev]
-			`assume` MadeChange
-		-- sync all buffered changes out to the disk image
-		-- may not be necessary, but seemed needed sometimes
-		-- when using the disk image right away.
-		& cmdProperty "sync" []
-			`assume` NoChange
-	  where
-	  	-- cannot use </> since the filepath is absolute
-		inmnt f = mnt ++ f
+  	-- cannot use </> since the filepath is absolute
+	inmnt f = mnt ++ f
 
-		inchroot cmd ps = cmdProperty "chroot" ([mnt, cmd] ++ ps)
+	inchroot cmd ps = cmdProperty "chroot" ([mnt, cmd] ++ ps)
 
-		haveosprober = doesFileExist (inmnt osprober)
-		osprober = "/etc/grub.d/30_os-prober"
+	haveosprober = doesFileExist (inmnt osprober)
+	osprober = "/etc/grub.d/30_os-prober"
 
-		-- It doesn't matter which loopdev we use; all
-		-- come from the same disk image, and it's the loop dev
-		-- for the whole disk image we seek.
-		wholediskloopdev = case loopdevs of
-			(l:_) -> wholeDiskLoopDev l
-			[] -> error "No loop devs provided!"
+	-- It doesn't matter which loopdev we use; all
+	-- come from the same disk image, and it's the loop dev
+	-- for the whole disk image we seek.
+	wholediskloopdev = case loopdevs of
+		(l:_) -> wholeDiskLoopDev l
+		[] -> error "No loop devs provided!"
 
 isChild :: FilePath -> Maybe MountPoint -> Bool
 isChild mntpt (Just d)
diff --git a/src/Propellor/Property/Grub.hs b/src/Propellor/Property/Grub.hs
--- a/src/Propellor/Property/Grub.hs
+++ b/src/Propellor/Property/Grub.hs
@@ -3,6 +3,7 @@
 import Propellor.Base
 import qualified Propellor.Property.File as File
 import qualified Propellor.Property.Apt as Apt
+import Propellor.Property.Chroot (inChroot)
 
 -- | Eg, \"hd0,0\" or \"xen/xvda1\"
 type GrubDevice = String
@@ -18,9 +19,10 @@
 -- | Installs the grub package. This does not make grub be used as the
 -- bootloader.
 --
--- This includes running update-grub.
+-- This includes running update-grub, unless it's run in a chroot.
 installed :: BIOS -> Property DebianLike
-installed bios = installed' bios `onChange` mkConfig
+installed bios = installed' bios 
+	`onChange` (check (not <$> inChroot) mkConfig)
 
 -- Run update-grub, to generate the grub boot menu. It will be
 -- automatically updated when kernel packages are installed.
