diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,14 @@
+propellor (4.6.2) unstable; urgency=medium
+
+  * Systemd.nspawned: Recent systemd versions such as 234 ignore
+    non-symlinks in /etc/systemd/system/multi-user.target.wants,
+    which was used to configure systemd-nspawn parameters. Instead,
+    use a service.d/local.conf file to configure that.
+  * Grub: Added bootsMounted property, a generalization of
+    DiskImage.grubBooted
+
+ -- Joey Hess <id@joeyh.name>  Fri, 28 Jul 2017 15:48:32 -0400
+
 propellor (4.6.1) unstable; urgency=medium
 
   * Added Network.dhcp' and Network.static', which allow specifying
diff --git a/debian/changelog b/debian/changelog
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+propellor (4.6.2) unstable; urgency=medium
+
+  * Systemd.nspawned: Recent systemd versions such as 234 ignore
+    non-symlinks in /etc/systemd/system/multi-user.target.wants,
+    which was used to configure systemd-nspawn parameters. Instead,
+    use a service.d/local.conf file to configure that.
+  * Grub: Added bootsMounted property, a generalization of
+    DiskImage.grubBooted
+
+ -- Joey Hess <id@joeyh.name>  Fri, 28 Jul 2017 15:48:32 -0400
+
 propellor (4.6.1) unstable; urgency=medium
 
   * Added Network.dhcp' and Network.static', which allow specifying
diff --git a/propellor.cabal b/propellor.cabal
--- a/propellor.cabal
+++ b/propellor.cabal
@@ -1,5 +1,5 @@
 Name: propellor
-Version: 4.6.1
+Version: 4.6.2
 Cabal-Version: >= 1.20
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
diff --git a/src/Propellor/Property/Bootstrap.hs b/src/Propellor/Property/Bootstrap.hs
--- a/src/Propellor/Property/Bootstrap.hs
+++ b/src/Propellor/Property/Bootstrap.hs
@@ -30,7 +30,7 @@
 bootstrapWith :: Bootstrapper -> Property (HasInfo + UnixLike)
 bootstrapWith b = pureInfoProperty desc (InfoVal b)
   where
-	desc = "bootstrapped with " ++ case b of
+	desc = "propellor bootstrapped with " ++ case b of
 		Robustly Stack -> "stack"
 		Robustly Cabal -> "cabal"
 		OSOnly -> "OS packages only"
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
@@ -363,35 +363,9 @@
 -- 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
+grubBooted mnt loopdevs = Grub.bootsMounted mnt wholediskloopdev
+	`describe` "disk image boots using grub"
   where
-  	-- cannot use </> since the filepath is absolute
-	inmnt f = mnt ++ f
-
-	inchroot cmd ps = cmdProperty "chroot" ([mnt, cmd] ++ ps)
-
-	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.
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.Mount
 import Propellor.Property.Chroot (inChroot)
 import Propellor.Types.Info
 import Propellor.Types.Bootloader
@@ -89,3 +90,46 @@
 	xenshim = scriptProperty ["grub-mkimage --prefix '(" ++ bootdev ++ ")/boot/grub' -c /boot/load.cf -O x86_64-xen /usr/lib/grub/x86_64-xen/*.mod > /boot/xen-shim"]
 		`assume` MadeChange
 		`describe` "/boot-xen-shim"
+
+-- | This is a version of `boots` that makes grub boot the system mounted
+-- at a particular directory. The OSDevice should be the underlying disk
+-- device that grub will be installed to (generally a whole disk, 
+-- not a partition).
+bootsMounted :: FilePath -> OSDevice -> Property Linux
+bootsMounted mnt wholediskdev = combineProperties desc $ 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" [wholediskdev]
+		`assume` MadeChange
+	& cleanupmounts
+	-- sync all buffered changes out to the disk in case it's
+	-- used right away
+	& cmdProperty "sync" []
+		`assume` NoChange
+  where
+	desc = "grub boots " ++ wholediskdev
+
+  	-- cannot use </> since the filepath is absolute
+	inmnt f = mnt ++ f
+
+	inchroot cmd ps = cmdProperty "chroot" ([mnt, cmd] ++ ps)
+
+	haveosprober = doesFileExist (inmnt osprober)
+	osprober = "/etc/grub.d/30_os-prober"
+
+	cleanupmounts :: Property Linux
+	cleanupmounts = property desc $ liftIO $ do
+		umountLazy (inmnt "/sys")
+		umountLazy (inmnt "/proc")
+		umountLazy (inmnt "/dev")
+		return NoChange
diff --git a/src/Propellor/Property/Systemd.hs b/src/Propellor/Property/Systemd.hs
--- a/src/Propellor/Property/Systemd.hs
+++ b/src/Propellor/Property/Systemd.hs
@@ -283,54 +283,42 @@
 
 	chroot = Chroot.Chroot loc builder Chroot.propagateChrootInfo h
 
--- | Sets up the service file for the container, and then starts
--- it running.
+-- | Sets up the service files for the container, using the
+-- systemd-nspawn@.service template, and starts it running.
 nspawnService :: Container -> ChrootCfg -> RevertableProperty Linux Linux
 nspawnService (Container name _ _) cfg = setup <!> teardown
   where
 	service = nspawnServiceName name
-	servicefile = "/etc/systemd/system/multi-user.target.wants" </> service
-
-	servicefilecontent = do
-		ls <- lines <$> readFile "/lib/systemd/system/systemd-nspawn@.service"
-		return $ unlines $
-			"# deployed by propellor" : map addparams ls
-	addparams l
-		| "ExecStart=" `isPrefixOf` l = unwords $
-			[ "ExecStart = /usr/bin/systemd-nspawn"
-			, "--quiet"
-			, "--keep-unit"
-			, "--boot"
-			, "--directory=" ++ containerDir name
-			, "--machine=%i"
-			] ++ nspawnServiceParams cfg
-		| otherwise = l
-
-	goodservicefile = (==)
-		<$> servicefilecontent
-		<*> catchDefaultIO "" (readFile servicefile)
-
-	writeservicefile :: Property Linux
-	writeservicefile = property servicefile $ makeChange $ do
-		c <- servicefilecontent
-		File.viaStableTmp (\t -> writeFile t c) servicefile
+	overridedir = "/etc/systemd/system" </> nspawnServiceName name ++ ".d"
+	overridefile = overridedir </> "local.conf"
+	overridecontent = 
+		[ "[Service]"
+		, "# Reset ExecStart from the template"
+		, "ExecStart="
+		, "ExecStart=/usr/bin/systemd-nspawn " ++ unwords nspawnparams
+		]
+	nspawnparams = 
+		[ "--quiet"
+		, "--keep-unit"
+		, "--boot"
+		, "--directory=" ++ containerDir name
+		, "--machine=" ++ name
+		] ++ nspawnServiceParams cfg
 
-	setupservicefile :: Property Linux
-	setupservicefile = check (not <$> goodservicefile) $
-		-- if it's running, it has the wrong configuration,
-		-- so stop it
-		stopped service
-			`requires` daemonReloaded
-			`requires` writeservicefile
+	overrideconfigured = File.hasContent overridefile overridecontent
+		`onChange` daemonReloaded
+		`requires` File.dirExists overridedir
 
 	setup :: Property Linux
 	setup = started service
-		`requires` setupservicefile
+		`requires` enabled service
+		`requires` overrideconfigured
 		`requires` machined
 
 	teardown :: Property Linux
-	teardown = check (doesFileExist servicefile) $
-		disabled service `requires` stopped service
+	teardown = stopped service
+		`before` disabled service
+		`before` File.notPresent overridefile
 
 nspawnServiceParams :: ChrootCfg -> [String]
 nspawnServiceParams NoChrootCfg = []
