diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,25 @@
 # Revision history for Arion
 
+## 0.2.2.0 -- 2024-12-11
+
+### Added
+
+* [`serivces.<name>.service.blkio_config`](https://docs.hercules-ci.com/arion/options#_services_name_service_blkio_config)
+* [`services.<name>.service.build.dockerfile`](https://docs.hercules-ci.com/arion/options#_services_name_service_build_dockerfile)
+* [`services.<name>.service.stop_grace_period`](https://docs.hercules-ci.com/arion/options#_services_name_service_stop_grace_period)
+* In the [NixOS deployment module], `virtualisation.arion.projects.<name>.serviceName` to override `<name>` used in systemd unit
+
+### Fixed
+
+* `nix repl` did not pass `--file` to Nix for file-based repls
+* `services.<name>.service.build.context` was ignored
+* `boot.tmpOnTmpfs` -> `boot.tmp.useTmpfs`
+* Remove lorri from local development environment, as it would fail silently
+
+### Removed
+
+* `defaultPackage` flake output. Use `packages.<system>.default` instead.
+
 ## 0.2.1.0 -- 2023-07-26
 
 ### Added
@@ -98,3 +118,6 @@
 
 * First released version. Released on an unsuspecting world.
 
+
+
+[NixOS deployment module]: https://docs.hercules-ci.com/arion/deployment#_nixos_module
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,3 @@
 import Distribution.Simple
+
 main = defaultMain
diff --git a/arion-compose.cabal b/arion-compose.cabal
--- a/arion-compose.cabal
+++ b/arion-compose.cabal
@@ -1,7 +1,7 @@
 cabal-version:       2.4
 
 name:                arion-compose
-version: 0.2.1.0
+version: 0.2.2.0
 synopsis:            Run docker-compose with help from Nix/NixOS
 description:         Arion is a tool for building and running applications that consist of multiple docker containers using NixOS modules. It has special support for docker images that are built with Nix, for a smooth development experience and improved performance.
 homepage:            https://github.com/hercules-ci/arion#readme
@@ -44,6 +44,15 @@
                    , protolude >= 0.2
                    , unix
   ghc-options:     -Wall
+  default-extensions:
+    BlockArguments
+    DeriveAnyClass
+    DeriveGeneric
+    DerivingStrategies
+    LambdaCase
+    NoFieldSelectors
+    OverloadedRecordDot
+    OverloadedStrings
 
 flag ghci
   default: False
diff --git a/src/haskell/exe/Main.hs b/src/haskell/exe/Main.hs
--- a/src/haskell/exe/Main.hs
+++ b/src/haskell/exe/Main.hs
@@ -1,80 +1,98 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ApplicativeDo #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-import Protolude hiding (Down, option)
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoImplicitPrelude #-}
 
-import           Arion.Nix
-import           Arion.Aeson
-import           Arion.Images (loadImages)
+import Arion.Aeson
 import qualified Arion.DockerCompose as DockerCompose
-import           Arion.Services (getDefaultExec)
-import           Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName))
-
-import Options.Applicative
+import Arion.ExtendedInfo (ExtendedInfo (images, projectName), loadExtendedInfoFromPath)
+import Arion.Images (loadImages)
+import Arion.Nix
+import Arion.Services (getDefaultExec)
 import Control.Monad.Fail
-
+import Data.Aeson (Value)
 import qualified Data.Text as T
 import qualified Data.Text.IO as T
-
-import Data.Aeson(Value)
-
+import Options.Applicative
+import Protolude hiding (Down, option)
 import System.Posix.User (getRealUserID)
 
-data CommonOptions =
-  CommonOptions
-    { files :: NonEmpty FilePath
-    , pkgs :: Text
-    , nixArgs :: [Text]
-    , prebuiltComposeFile :: Maybe FilePath
-    , noAnsi :: Bool
-    , compatibility :: Bool
-    , logLevel :: Maybe Text
-    }
-  deriving (Show)
+data CommonOptions = CommonOptions
+  { files :: NonEmpty FilePath,
+    pkgs :: Text,
+    nixArgs :: [Text],
+    prebuiltComposeFile :: Maybe FilePath,
+    noAnsi :: Bool,
+    compatibility :: Bool,
+    logLevel :: Maybe Text
+  }
+  deriving stock (Show)
 
-newtype DockerComposeArgs =
-  DockerComposeArgs { unDockerComposeArgs :: [Text] }
+newtype DockerComposeArgs = DockerComposeArgs {unDockerComposeArgs :: [Text]}
 
 ensureConfigFile :: [FilePath] -> NonEmpty FilePath
-ensureConfigFile []     = "./arion-compose.nix" :| []
-ensureConfigFile (x:xs) = x :| xs
+ensureConfigFile [] = "./arion-compose.nix" :| []
+ensureConfigFile (x : xs) = x :| xs
 
 parseOptions :: Parser CommonOptions
 parseOptions = do
-    files <-
-      ensureConfigFile <$>
-        many (strOption
-               (  short 'f'
-               <> long "file"
-               <> metavar "FILE"
-               <> help "Use FILE instead of the default ./arion-compose.nix. \
-                        \Can be specified multiple times for a merged configuration" ))
-    pkgs <- T.pack <$> strOption
-          (  short 'p'
-          <> long "pkgs"
-          <> metavar "EXPR"
-          <> showDefault
-          <> value "./arion-pkgs.nix"
-          <> help "Use Nix expression EXPR to get the Nixpkgs attrset used for bootstrapping \
-                   \and evaluating the configuration." )
-    showTrace <- flag False True (long "show-trace"
-                    <> help "Causes Nix to print out a stack trace in case of Nix expression evaluation errors. Specify before command.")
-    -- TODO --option support (https://github.com/pcapriotti/optparse-applicative/issues/284)
-    userNixArgs <- many (T.pack <$> strOption (long "nix-arg" <> metavar "ARG" <> help "Pass an extra argument to nix. Example: --nix-arg --option --nix-arg substitute --nix-arg false"))
-    prebuiltComposeFile <- optional $ strOption
-               (  long "prebuilt-file"
-               <> metavar "JSONFILE"
-               <> help "Do not evaluate and use the prebuilt JSONFILE instead. Causes other evaluation-related options to be ignored." )
-    noAnsi <- flag False True (long "no-ansi"
-                    <> help "Avoid ANSI control sequences")
-    compatibility <- flag False True (long "no-ansi"
-                    <> help "If set, Docker Compose will attempt to convert deploy keys in v3 files to their non-Swarm equivalent")
-    logLevel <- optional $ fmap T.pack $ strOption (long "log-level" <> metavar "LEVEL" <> help "Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
-    pure $
-      let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
-      in CommonOptions{..}
+  files <-
+    ensureConfigFile
+      <$> many
+        ( strOption
+            ( short 'f'
+                <> long "file"
+                <> metavar "FILE"
+                <> help
+                  "Use FILE instead of the default ./arion-compose.nix. \
+                  \Can be specified multiple times for a merged configuration"
+            )
+        )
+  pkgs <-
+    T.pack
+      <$> strOption
+        ( short 'p'
+            <> long "pkgs"
+            <> metavar "EXPR"
+            <> showDefault
+            <> value "./arion-pkgs.nix"
+            <> help
+              "Use Nix expression EXPR to get the Nixpkgs attrset used for bootstrapping \
+              \and evaluating the configuration."
+        )
+  showTrace <-
+    flag
+      False
+      True
+      ( long "show-trace"
+          <> help "Causes Nix to print out a stack trace in case of Nix expression evaluation errors. Specify before command."
+      )
+  -- TODO --option support (https://github.com/pcapriotti/optparse-applicative/issues/284)
+  userNixArgs <- many (T.pack <$> strOption (long "nix-arg" <> metavar "ARG" <> help "Pass an extra argument to nix. Example: --nix-arg --option --nix-arg substitute --nix-arg false"))
+  prebuiltComposeFile <-
+    optional $
+      strOption
+        ( long "prebuilt-file"
+            <> metavar "JSONFILE"
+            <> help "Do not evaluate and use the prebuilt JSONFILE instead. Causes other evaluation-related options to be ignored."
+        )
+  noAnsi <-
+    flag
+      False
+      True
+      ( long "no-ansi"
+          <> help "Avoid ANSI control sequences"
+      )
+  compatibility <-
+    flag
+      False
+      True
+      ( long "no-ansi"
+          <> help "If set, Docker Compose will attempt to convert deploy keys in v3 files to their non-Swarm equivalent"
+      )
+  logLevel <- optional $ fmap T.pack $ strOption (long "log-level" <> metavar "LEVEL" <> help "Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
+  pure $
+    let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
+     in CommonOptions {..}
 
 textArgument :: Mod ArgumentFields [Char] -> Parser Text
 textArgument = fmap T.pack . strArgument
@@ -82,41 +100,39 @@
 parseCommand :: Parser (CommonOptions -> IO ())
 parseCommand =
   hsubparser
-    (    command "cat" (info (pure runCat) (progDesc "Spit out the docker compose file as JSON" <> fullDesc))
-      <> command "repl" (info (pure runRepl) (progDesc "Start a nix repl for the whole composition" <> fullDesc))
-      <> command "exec" (info (parseExecCommand) (progDesc "Execute a command in a running container" <> fullDesc))
-    )
-  <|>
-  hsubparser
-    (    commandDC runBuildAndDC "build" "Build or rebuild services"
-      <> commandDC runBuildAndDC "bundle" "Generate a Docker bundle from the Compose file"
-      <> commandDC runEvalAndDC "config" "Validate and view the Compose file"
-      <> commandDC runBuildAndDC "create" "Create services"
-      <> commandDC runEvalAndDC "down" "Stop and remove containers, networks, images, and volumes"
-      <> commandDC runEvalAndDC "events" "Receive real time events from containers"
-      <> commandDC runDC "help" "Get help on a command"
-      <> commandDC runEvalAndDC "images" "List images"
-      <> commandDC runEvalAndDC "kill" "Kill containers"
-      <> commandDC runEvalAndDC "logs" "View output from containers"
-      <> commandDC runEvalAndDC "pause" "Pause services"
-      <> commandDC runEvalAndDC "port" "Print the public port for a port binding"
-      <> commandDC runEvalAndDC "ps" "List containers"
-      <> commandDC runBuildAndDC "pull" "Pull service images"
-      <> commandDC runBuildAndDC "push" "Push service images"
-      <> commandDC runBuildAndDC "restart" "Restart services"
-      <> commandDC runEvalAndDC "rm" "Remove stopped containers"
-      <> commandDC runBuildAndDC "run" "Run a one-off command"
-      <> commandDC runBuildAndDC "scale" "Set number of containers for a service"
-      <> commandDC runBuildAndDC "start" "Start services"
-      <> commandDC runEvalAndDC "stop" "Stop services"
-      <> commandDC runEvalAndDC "top" "Display the running processes"
-      <> commandDC runEvalAndDC "unpause" "Unpause services"
-      <> commandDC runBuildAndDC "up" "Create and start containers"
-      <> commandDC runDC "version" "Show the Docker-Compose version information"
-
-      <> metavar "DOCKER-COMPOSE-COMMAND"
-      <> commandGroup "Docker Compose Commands:"
+    ( command "cat" (info (pure runCat) (progDesc "Spit out the docker compose file as JSON" <> fullDesc))
+        <> command "repl" (info (pure runRepl) (progDesc "Start a nix repl for the whole composition" <> fullDesc))
+        <> command "exec" (info (parseExecCommand) (progDesc "Execute a command in a running container" <> fullDesc))
     )
+    <|> hsubparser
+      ( commandDC runBuildAndDC "build" "Build or rebuild services"
+          <> commandDC runBuildAndDC "bundle" "Generate a Docker bundle from the Compose file"
+          <> commandDC runEvalAndDC "config" "Validate and view the Compose file"
+          <> commandDC runBuildAndDC "create" "Create services"
+          <> commandDC runEvalAndDC "down" "Stop and remove containers, networks, images, and volumes"
+          <> commandDC runEvalAndDC "events" "Receive real time events from containers"
+          <> commandDC runDC "help" "Get help on a command"
+          <> commandDC runEvalAndDC "images" "List images"
+          <> commandDC runEvalAndDC "kill" "Kill containers"
+          <> commandDC runEvalAndDC "logs" "View output from containers"
+          <> commandDC runEvalAndDC "pause" "Pause services"
+          <> commandDC runEvalAndDC "port" "Print the public port for a port binding"
+          <> commandDC runEvalAndDC "ps" "List containers"
+          <> commandDC runBuildAndDC "pull" "Pull service images"
+          <> commandDC runBuildAndDC "push" "Push service images"
+          <> commandDC runBuildAndDC "restart" "Restart services"
+          <> commandDC runEvalAndDC "rm" "Remove stopped containers"
+          <> commandDC runBuildAndDC "run" "Run a one-off command"
+          <> commandDC runBuildAndDC "scale" "Set number of containers for a service"
+          <> commandDC runBuildAndDC "start" "Start services"
+          <> commandDC runEvalAndDC "stop" "Stop services"
+          <> commandDC runEvalAndDC "top" "Display the running processes"
+          <> commandDC runEvalAndDC "unpause" "Unpause services"
+          <> commandDC runBuildAndDC "up" "Create and start containers"
+          <> commandDC runDC "version" "Show the Docker-Compose version information"
+          <> metavar "DOCKER-COMPOSE-COMMAND"
+          <> commandGroup "Docker Compose Commands:"
+      )
 
 parseAll :: Parser (IO ())
 parseAll =
@@ -124,30 +140,31 @@
 
 parseDockerComposeArgs :: Parser DockerComposeArgs
 parseDockerComposeArgs =
-  DockerComposeArgs <$>
-    many (argument (T.pack <$> str) (metavar "DOCKER-COMPOSE ARGS..."))
+  DockerComposeArgs
+    <$> many (argument (T.pack <$> str) (metavar "DOCKER-COMPOSE ARGS..."))
 
-commandDC
-  :: (Text -> DockerComposeArgs -> CommonOptions -> IO ())
-  -> Text
-  -> Text
-  -> Mod CommandFields (CommonOptions -> IO ())
+commandDC ::
+  (Text -> DockerComposeArgs -> CommonOptions -> IO ()) ->
+  Text ->
+  Text ->
+  Mod CommandFields (CommonOptions -> IO ())
 commandDC run cmdStr helpText =
   command
     (T.unpack cmdStr)
-    (info
-      (run cmdStr <$> parseDockerComposeArgs)
-      (progDesc (T.unpack helpText) <> fullDesc <> forwardOptions))
-
+    ( info
+        (run cmdStr <$> parseDockerComposeArgs)
+        (progDesc (T.unpack helpText) <> fullDesc <> forwardOptions)
+    )
 
 --------------------------------------------------------------------------------
 
 runDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
 runDC cmd (DockerComposeArgs args) _opts = do
-  DockerCompose.run DockerCompose.Args
-    { files = []
-    , otherArgs = [cmd] ++ args
-    }
+  DockerCompose.run
+    DockerCompose.Args
+      { files = [],
+        otherArgs = [cmd] ++ args
+      }
 
 runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
 runBuildAndDC cmd dopts opts = do
@@ -160,32 +177,34 @@
 callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO ()
 callDC cmd dopts opts shouldLoadImages path = do
   extendedInfo <- loadExtendedInfoFromPath path
-  when shouldLoadImages $ loadImages (images extendedInfo)
+  when shouldLoadImages $ loadImages (extendedInfo.images)
   let firstOpts = projectArgs extendedInfo <> commonArgs opts
-  DockerCompose.run DockerCompose.Args
-    { files = [path]
-    , otherArgs = firstOpts ++ [cmd] ++ unDockerComposeArgs dopts
-    }
+  DockerCompose.run
+    DockerCompose.Args
+      { files = [path],
+        otherArgs = firstOpts ++ [cmd] ++ dopts.unDockerComposeArgs
+      }
 
 projectArgs :: ExtendedInfo -> [Text]
 projectArgs extendedInfo =
   do
-    n <- toList (projectName extendedInfo)
+    n <- toList (extendedInfo.projectName)
     ["--project-name", n]
 
 commonArgs :: CommonOptions -> [Text]
-commonArgs opts = do
-    guard (noAnsi opts)
+commonArgs opts =
+  do
+    guard opts.noAnsi
     ["--no-ansi"]
-  <> do
-    guard (compatibility opts)
-    ["--compatibility"]
-  <> do
-    l <- toList (logLevel opts)
-    ["--log-level", l]
+    <> do
+      guard opts.compatibility
+      ["--compatibility"]
+    <> do
+      l <- toList opts.logLevel
+      ["--log-level", l]
 
 withBuiltComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
-withBuiltComposeFile opts cont = case prebuiltComposeFile opts of
+withBuiltComposeFile opts cont = case opts.prebuiltComposeFile of
   Just prebuilt -> do
     cont prebuilt
   Nothing -> do
@@ -193,7 +212,7 @@
     Arion.Nix.withBuiltComposition args cont
 
 withComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
-withComposeFile opts cont = case prebuiltComposeFile opts of
+withComposeFile opts cont = case opts.prebuiltComposeFile of
   Just prebuilt -> do
     cont prebuilt
   Nothing -> do
@@ -201,7 +220,7 @@
     Arion.Nix.withEvaluatedComposition args cont
 
 getComposeValue :: CommonOptions -> IO Value
-getComposeValue opts = case prebuiltComposeFile opts of
+getComposeValue opts = case opts.prebuiltComposeFile of
   Just prebuilt -> do
     decodeFile prebuilt
   Nothing -> do
@@ -211,14 +230,15 @@
 defaultEvaluationArgs :: CommonOptions -> IO EvaluationArgs
 defaultEvaluationArgs co = do
   uid <- getRealUserID
-  pure EvaluationArgs
-    { evalUid = fromIntegral uid
-    , evalModules = files co
-    , evalPkgs = pkgs co
-    , evalWorkDir = Nothing
-    , evalMode = ReadWrite
-    , evalUserArgs = nixArgs co
-    }
+  pure
+    EvaluationArgs
+      { posixUID = fromIntegral uid,
+        evalModulesFile = co.files,
+        pkgsExpr = co.pkgs,
+        workDir = Nothing,
+        mode = ReadWrite,
+        extraNixArgs = co.nixArgs
+      }
 
 runCat :: CommonOptions -> IO ()
 runCat co = do
@@ -250,33 +270,37 @@
 noTTYFlag = flag False True (short 'T' <> help "Disable pseudo-tty allocation. By default `exec` allocates a TTY.")
 
 indexOption :: Parser Int
-indexOption = option
-  (auto >>= \i -> i <$ unless (i >= 1) (fail "container index must be >= 1"))
-  (long "index" <> value 1 <> help "Index of the container if there are multiple instances of a service.")
+indexOption =
+  option
+    (auto >>= \i -> i <$ unless (i >= 1) (fail "container index must be >= 1"))
+    (long "index" <> value 1 <> help "Index of the container if there are multiple instances of a service.")
 
 envOption :: Parser (Text, Text)
 envOption = option (auto >>= spl) (long "env" <> short 'e' <> help "Set environment variables (can be used multiple times, not supported in Docker API < 1.25)")
-  where spl s = case T.break (== '=') s of
-          (_, "") -> fail "--env parameter needs to combine key and value with = sign"
-          (k, ev) -> pure (k, T.drop 1 ev)
+  where
+    spl s = case T.break (== '=') s of
+      (_, "") -> fail "--env parameter needs to combine key and value with = sign"
+      (k, ev) -> pure (k, T.drop 1 ev)
 
 workdirOption :: Parser Text
 workdirOption = strOption (long "workdir" <> short 'w' <> metavar "DIR" <> help "Working directory in which to start the command in the container.")
 
 parseExecCommand :: Parser (CommonOptions -> IO ())
-parseExecCommand = runExec
-  <$> detachFlag
-  <*> privilegedFlag
-  <*> optional userOption
-  <*> noTTYFlag
-  <*> indexOption
-  <*> many envOption
-  <*> optional workdirOption
-  <*> textArgument (metavar "SERVICE")
-  <*> orEmpty' (
-      (:) <$> argument (T.pack <$> str) (metavar "COMMAND")
+parseExecCommand =
+  runExec
+    <$> detachFlag
+    <*> privilegedFlag
+    <*> optional userOption
+    <*> noTTYFlag
+    <*> indexOption
+    <*> many envOption
+    <*> optional workdirOption
+    <*> textArgument (metavar "SERVICE")
+    <*> orEmpty'
+      ( (:)
+          <$> argument (T.pack <$> str) (metavar "COMMAND")
           <*> many (argument (T.pack <$> str) (metavar "ARG"))
-  )
+      )
 
 orEmpty' :: (Alternative f, Monoid a) => f a -> f a
 orEmpty' m = fromMaybe mempty <$> optional m
@@ -298,27 +322,28 @@
     let commandAndArgs' = case commandAndArgs'' of
           [] -> ["/bin/sh"]
           x -> x
-  
-    let args = concat
-          [ ["exec"]
-          , ("--detach" <$ guard detach :: [Text])
-          , "--privileged" <$ guard privileged
-          , "-T" <$ guard noTTY
-          , (\(k, v) -> ["--env", k <> "=" <> v]) =<< envs
-          , join $ toList (user <&> \u -> ["--user", u])
-          , ["--index", show index]
-          , join $ toList (workDir <&> \w -> ["--workdir", w])
-          , [service]
-          , commandAndArgs'
-          ]
-    DockerCompose.run DockerCompose.Args
-      { files = [path]
-      , otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
-      }
 
+    let args =
+          concat
+            [ ["exec"],
+              ("--detach" <$ guard detach :: [Text]),
+              "--privileged" <$ guard privileged,
+              "-T" <$ guard noTTY,
+              (\(k, v) -> ["--env", k <> "=" <> v]) =<< envs,
+              join $ toList (user <&> \u -> ["--user", u]),
+              ["--index", show index],
+              join $ toList (workDir <&> \w -> ["--workdir", w]),
+              [service],
+              commandAndArgs'
+            ]
+    DockerCompose.run
+      DockerCompose.Args
+        { files = [path],
+          otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
+        }
+
 main :: IO ()
-main = 
+main =
   (join . arionExecParser) (info (parseAll <**> helper) fullDesc)
   where
     arionExecParser = customExecParser (prefs showHelpOnEmpty)
-
diff --git a/src/haskell/lib/Arion/Aeson.hs b/src/haskell/lib/Arion/Aeson.hs
--- a/src/haskell/lib/Arion/Aeson.hs
+++ b/src/haskell/lib/Arion/Aeson.hs
@@ -1,25 +1,27 @@
 module Arion.Aeson where
 
-import Prelude ()
-import           Data.Aeson
-import qualified Data.ByteString.Lazy          as BL
-import qualified Data.Text.Lazy                as TL
-import qualified Data.Text.Lazy.Builder        as TB
+import Data.Aeson
+import Data.Aeson.Encode.Pretty
+  ( confCompare,
+    confTrailingNewline,
+    defConfig,
+  )
 import qualified Data.Aeson.Encode.Pretty
-import           Data.Aeson.Encode.Pretty       ( defConfig
-                                                , confCompare
-                                                , confTrailingNewline
-                                                )
-import           Protolude
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as TB
+import Protolude
+import Prelude ()
 
-pretty :: ToJSON a => a -> Text
+pretty :: (ToJSON a) => a -> Text
 pretty =
   TL.toStrict
     . TB.toLazyText
     . Data.Aeson.Encode.Pretty.encodePrettyToTextBuilder' config
-  where config = defConfig { confCompare = compare, confTrailingNewline = True }
+  where
+    config = defConfig {confCompare = compare, confTrailingNewline = True}
 
-decodeFile :: FromJSON a => FilePath -> IO a
+decodeFile :: (FromJSON a) => FilePath -> IO a
 decodeFile fp = do
   b <- BL.readFile fp
   case eitherDecode b of
diff --git a/src/haskell/lib/Arion/DockerCompose.hs b/src/haskell/lib/Arion/DockerCompose.hs
--- a/src/haskell/lib/Arion/DockerCompose.hs
+++ b/src/haskell/lib/Arion/DockerCompose.hs
@@ -1,26 +1,24 @@
-{-# LANGUAGE OverloadedStrings #-}
 module Arion.DockerCompose where
 
-import           Prelude                        ( )
-import           Protolude
-import           System.Process
+import Protolude
+import System.Process
+import Prelude ()
 
 data Args = Args
-  { files :: [FilePath]
-  , otherArgs :: [Text]
+  { files :: [FilePath],
+    otherArgs :: [Text]
   }
 
 run :: Args -> IO ()
 run args = do
-  let fileArgs = files args >>= \f -> ["--file", f]
-      allArgs  = fileArgs ++ map toS (otherArgs args)
+  let fileArgs = args.files >>= \f -> ["--file", f]
+      allArgs = fileArgs ++ map toS args.otherArgs
 
       procSpec = proc "docker-compose" allArgs
 
   -- hPutStrLn stderr ("Running docker-compose with " <> show allArgs :: Text)
 
   withCreateProcess procSpec $ \_in _out _err procHandle -> do
-
     exitCode <- waitForProcess procHandle
 
     case exitCode of
diff --git a/src/haskell/lib/Arion/ExtendedInfo.hs b/src/haskell/lib/Arion/ExtendedInfo.hs
--- a/src/haskell/lib/Arion/ExtendedInfo.hs
+++ b/src/haskell/lib/Arion/ExtendedInfo.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-
 
 Parses the x-arion field in the generated compose file.
@@ -8,30 +5,36 @@
 -}
 module Arion.ExtendedInfo where
 
-import Prelude()
-import Protolude
-import Data.Aeson as Aeson
 import Arion.Aeson
 import Control.Lens
+import Data.Aeson as Aeson
 import Data.Aeson.Lens
+import Protolude
+import Prelude ()
 
 data Image = Image
-  { image :: Maybe Text -- ^ image tar.gz file path
-  , imageExe :: Maybe Text -- ^ path to exe producing image tar
-  , imageName :: Text
-  , imageTag :: Text
-  } deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON)
+  { -- | image tar.gz file path
+    image :: Maybe Text,
+    -- | path to exe producing image tar
+    imageExe :: Maybe Text,
+    imageName :: Text,
+    imageTag :: Text
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
 
-data ExtendedInfo = ExtendedInfo {
-    projectName :: Maybe Text,
+data ExtendedInfo = ExtendedInfo
+  { projectName :: Maybe Text,
     images :: [Image]
-  } deriving (Eq, Show)
+  }
+  deriving stock (Eq, Show)
 
 loadExtendedInfoFromPath :: FilePath -> IO ExtendedInfo
 loadExtendedInfoFromPath fp = do
   v <- decodeFile fp
-  pure ExtendedInfo {
-    -- TODO: use aeson derived instance?
-    projectName = v ^? key "x-arion" . key "project" . key "name" . _String,
-    images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON
-  }
+  pure
+    ExtendedInfo
+      { -- TODO: use aeson derived instance?
+        projectName = v ^? key "x-arion" . key "project" . key "name" . _String,
+        images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON
+      }
diff --git a/src/haskell/lib/Arion/Images.hs b/src/haskell/lib/Arion/Images.hs
--- a/src/haskell/lib/Arion/Images.hs
+++ b/src/haskell/lib/Arion/Images.hs
@@ -1,54 +1,49 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE OverloadedStrings #-}
 module Arion.Images
-  ( loadImages
-  ) where
+  ( loadImages,
+  )
+where
 
-import Prelude()
+import Arion.ExtendedInfo (Image (..))
+import qualified Data.Text as T
 import Protolude hiding (to)
-
 import qualified System.Process as Process
-import qualified Data.Text as T
-
-import Arion.ExtendedInfo (Image(..))
+import Prelude ()
 
 type TaggedImage = Text
 
 -- | Subject to change
 loadImages :: [Image] -> IO ()
 loadImages requestedImages = do
-
   loaded <- getDockerImages
 
-  let
-    isNew i =
-      -- On docker, the image name is unmodified
-      (imageName i <> ":" <> imageTag i) `notElem` loaded
-      -- On podman, you used to automatically get a localhost prefix
-      -- however, since NixOS 22.05, this expected to be part of the name instead
-        && ("localhost/" <> imageName i <> ":" <> imageTag i) `notElem` loaded
+  let isNew :: Image -> Bool
+      isNew i =
+        -- On docker, the image name is unmodified
+        (i.imageName <> ":" <> i.imageTag) `notElem` loaded
+          -- On podman, you used to automatically get a localhost prefix
+          -- however, since NixOS 22.05, this expected to be part of the name instead
+          && ("localhost/" <> i.imageName <> ":" <> i.imageTag) `notElem` loaded
 
   traverse_ loadImage . filter isNew $ requestedImages
 
 loadImage :: Image -> IO ()
-loadImage Image { image = Just imgPath, imageName = name } =
+loadImage Image {image = Just imgPath, imageName = name} =
   withFile (toS imgPath) ReadMode $ \fileHandle -> do
-  let procSpec = (Process.proc "docker" [ "load" ]) {
-          Process.std_in = Process.UseHandle fileHandle
-        }
-  Process.withCreateProcess procSpec $ \_in _out _err procHandle -> do
-    e <- Process.waitForProcess procHandle
-    case e of
-      ExitSuccess -> pass
-      ExitFailure code ->
-        panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath
-
-loadImage Image { imageExe = Just imgExe, imageName = name } = do
-  let loadSpec = (Process.proc "docker" [ "load" ]) { Process.std_in = Process.CreatePipe }
+    let procSpec =
+          (Process.proc "docker" ["load"])
+            { Process.std_in = Process.UseHandle fileHandle
+            }
+    Process.withCreateProcess procSpec $ \_in _out _err procHandle -> do
+      e <- Process.waitForProcess procHandle
+      case e of
+        ExitSuccess -> pass
+        ExitFailure code ->
+          panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath
+loadImage Image {imageExe = Just imgExe, imageName = name} = do
+  let loadSpec = (Process.proc "docker" ["load"]) {Process.std_in = Process.CreatePipe}
   Process.withCreateProcess loadSpec $ \(Just inHandle) _out _err loadProcHandle -> do
     let streamSpec = Process.proc (toS imgExe) []
-    Process.withCreateProcess streamSpec { Process.std_out = Process.UseHandle inHandle } $ \_ _ _ streamProcHandle ->
+    Process.withCreateProcess streamSpec {Process.std_out = Process.UseHandle inHandle} $ \_ _ _ streamProcHandle ->
       withAsync (Process.waitForProcess loadProcHandle) $ \loadExitAsync ->
         withAsync (Process.waitForProcess streamProcHandle) $ \streamExitAsync -> do
           r <- waitEither loadExitAsync streamExitAsync
@@ -61,12 +56,10 @@
             ExitFailure code -> panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " produced by executable " <> imgExe
             _ -> pass
           pass
-
-loadImage Image { imageName = name } = do
+loadImage Image {imageName = name} = do
   panic $ "image " <> name <> " doesn't specify an image file or imageExe executable"
 
-
 getDockerImages :: IO [TaggedImage]
 getDockerImages = do
-  let procSpec = Process.proc "docker" [ "images",  "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ]
+  let procSpec = Process.proc "docker" ["images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}"]
   map toS . T.lines . toS <$> Process.readCreateProcess procSpec ""
diff --git a/src/haskell/lib/Arion/Nix.hs b/src/haskell/lib/Arion/Nix.hs
--- a/src/haskell/lib/Arion/Nix.hs
+++ b/src/haskell/lib/Arion/Nix.hs
@@ -1,65 +1,64 @@
-{-# LANGUAGE OverloadedStrings #-}
 module Arion.Nix
-  ( evaluateComposition
-  , withEvaluatedComposition
-  , buildComposition
-  , withBuiltComposition
-  , replForComposition
-  , EvaluationArgs(..)
-  , EvaluationMode(..)
-  ) where
+  ( evaluateComposition,
+    withEvaluatedComposition,
+    buildComposition,
+    withBuiltComposition,
+    replForComposition,
+    EvaluationArgs (..),
+    EvaluationMode (..),
+  )
+where
 
-import           Prelude                        ( )
-import           Protolude
-import           Arion.Aeson                    ( pretty )
-import           Data.Aeson
+import Arion.Aeson (pretty)
+import Control.Arrow ((>>>))
+import Data.Aeson
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.List.NonEmpty as NE
 import qualified Data.String
-import qualified System.Directory              as Directory
-import           System.Process
-import qualified Data.ByteString.Lazy          as BL
-import           Paths_arion_compose
-
-import qualified Data.Text.IO                  as T
-
-import qualified Data.List.NonEmpty            as NE
-
-import           Control.Arrow                  ( (>>>) )
-import           System.IO.Temp                 ( withTempFile )
-import           System.IO                      ( hClose )
+import qualified Data.Text.IO as T
+import Paths_arion_compose
+import Protolude
+import qualified System.Directory as Directory
+import System.IO (hClose)
+import System.IO.Temp (withTempFile)
+import System.Process
+import Prelude ()
 
-data EvaluationMode =
-  ReadWrite | ReadOnly
+data EvaluationMode
+  = ReadWrite
+  | ReadOnly
 
 data EvaluationArgs = EvaluationArgs
- { evalUid :: Int
- , evalModules :: NonEmpty FilePath
- , evalPkgs :: Text
- , evalWorkDir :: Maybe FilePath
- , evalMode :: EvaluationMode
- , evalUserArgs :: [Text]
- }
+  { posixUID :: Int,
+    evalModulesFile :: NonEmpty FilePath,
+    pkgsExpr :: Text,
+    workDir :: Maybe FilePath,
+    mode :: EvaluationMode,
+    extraNixArgs :: [Text]
+  }
 
 evaluateComposition :: EvaluationArgs -> IO Value
 evaluateComposition ea = do
   evalComposition <- getEvalCompositionFile
   let commandArgs =
-        [ "--eval"
-        , "--strict"
-        , "--json"
-        , "--attr"
-        , "config.out.dockerComposeYamlAttrs"
+        [ "--eval",
+          "--strict",
+          "--json",
+          "--attr",
+          "config.out.dockerComposeYamlAttrs"
         ]
       args =
-        [ evalComposition ]
-        ++ commandArgs
-        ++ modeArguments (evalMode ea)
-        ++ argArgs ea
-        ++ map toS (evalUserArgs ea)
-      procSpec = (proc "nix-instantiate" args)
-        { cwd = evalWorkDir ea
-        , std_out = CreatePipe
-        }
-  
+        [evalComposition]
+          ++ commandArgs
+          ++ modeArguments ea.mode
+          ++ argArgs ea
+          ++ map toS ea.extraNixArgs
+      procSpec =
+        (proc "nix-instantiate" args)
+          { cwd = ea.workDir,
+            std_out = CreatePipe
+          }
+
   withCreateProcess procSpec $ \_in outHM _err procHandle -> do
     let outHandle = fromMaybe (panic "stdout missing") outHM
 
@@ -77,7 +76,7 @@
 
     case v of
       Right r -> pure r
-      Left  e -> throwIO $ FatalError ("Couldn't parse nix-instantiate output" <> show e)
+      Left e -> throwIO $ FatalError ("Couldn't parse nix-instantiate output" <> show e)
 
 -- | Run with docker-compose.yaml tmpfile
 withEvaluatedComposition :: EvaluationArgs -> (FilePath -> IO r) -> IO r
@@ -88,25 +87,23 @@
     hClose yamlHandle
     f path
 
-
 buildComposition :: FilePath -> EvaluationArgs -> IO ()
 buildComposition outLink ea = do
   evalComposition <- getEvalCompositionFile
   let commandArgs =
-        [ "--attr"
-        , "config.out.dockerComposeYaml"
-        , "--out-link"
-        , outLink
+        [ "--attr",
+          "config.out.dockerComposeYaml",
+          "--out-link",
+          outLink
         ]
       args =
-        [ evalComposition ]
-        ++ commandArgs
-        ++ argArgs ea
-        ++ map toS (evalUserArgs ea)
-      procSpec = (proc "nix-build" args) { cwd = evalWorkDir ea }
-  
-  withCreateProcess procSpec $ \_in _out _err procHandle -> do
+        [evalComposition]
+          ++ commandArgs
+          ++ argArgs ea
+          ++ map toS ea.extraNixArgs
+      procSpec = (proc "nix-build" args) {cwd = ea.workDir}
 
+  withCreateProcess procSpec $ \_in _out _err procHandle -> do
     exitCode <- waitForProcess procHandle
 
     case exitCode of
@@ -126,58 +123,57 @@
     buildComposition path ea
     f path
 
-
 replForComposition :: EvaluationArgs -> IO ()
 replForComposition ea = do
-    evalComposition <- getEvalCompositionFile
-    let args =
-          [ "repl", evalComposition ]
+  evalComposition <- getEvalCompositionFile
+  let args =
+        ["repl", "--file", evalComposition]
           ++ argArgs ea
-          ++ map toS (evalUserArgs ea)
-        procSpec = (proc "nix" args) { cwd = evalWorkDir ea }
-    
-    withCreateProcess procSpec $ \_in _out _err procHandle -> do
+          ++ map toS ea.extraNixArgs
+      procSpec = (proc "nix" args) {cwd = ea.workDir}
 
-      exitCode <- waitForProcess procHandle
+  withCreateProcess procSpec $ \_in _out _err procHandle -> do
+    exitCode <- waitForProcess procHandle
 
-      case exitCode of
-        ExitSuccess -> pass
-        ExitFailure 1 -> exitFailure
-        ExitFailure {} -> do
-          throwIO $ FatalError $ "nix repl failed with " <> show exitCode
+    case exitCode of
+      ExitSuccess -> pass
+      ExitFailure 1 -> exitFailure
+      ExitFailure {} -> do
+        throwIO $ FatalError $ "nix repl failed with " <> show exitCode
 
 argArgs :: EvaluationArgs -> [[Char]]
 argArgs ea =
-      [ "--argstr"
-      , "uid"
-      , show $ evalUid ea
-      , "--arg"
-      , "modules"
-      , modulesNixExpr $ evalModules ea
-      , "--arg"
-      , "pkgs"
-      , toS $ evalPkgs ea
-      ]
+  [ "--argstr",
+    "uid",
+    show ea.posixUID,
+    "--arg",
+    "modules",
+    modulesNixExpr ea.evalModulesFile,
+    "--arg",
+    "pkgs",
+    toS ea.pkgsExpr
+  ]
 
 getEvalCompositionFile :: IO FilePath
 getEvalCompositionFile = getDataFileName "nix/eval-composition.nix"
 
 modeArguments :: EvaluationMode -> [[Char]]
-modeArguments ReadWrite = [ "--read-write-mode" ]
-modeArguments ReadOnly = [ "--readonly-mode" ]
+modeArguments ReadWrite = ["--read-write-mode"]
+modeArguments ReadOnly = ["--readonly-mode"]
 
 modulesNixExpr :: NonEmpty FilePath -> [Char]
 modulesNixExpr =
   NE.toList >>> fmap pathExpr >>> Data.String.unwords >>> wrapList
- where
-  pathExpr :: FilePath -> [Char]
-  pathExpr path | isAbsolute path = "(/. + \"/${" <> toNixStringLiteral path <> "}\")"
-                | otherwise       = "(./. + \"/${" <> toNixStringLiteral path <> "}\")"
+  where
+    pathExpr :: FilePath -> [Char]
+    pathExpr path
+      | isAbsolute path = "(/. + \"/${" <> toNixStringLiteral path <> "}\")"
+      | otherwise = "(./. + \"/${" <> toNixStringLiteral path <> "}\")"
 
-  isAbsolute ('/' : _) = True
-  isAbsolute _         = False
+    isAbsolute ('/' : _) = True
+    isAbsolute _ = False
 
-  wrapList s = "[ " <> s <> " ]"
+    wrapList s = "[ " <> s <> " ]"
 
 toNixStringLiteral :: [Char] -> [Char]
 toNixStringLiteral = show -- FIXME: custom escaping including '$'
diff --git a/src/haskell/lib/Arion/Services.hs b/src/haskell/lib/Arion/Services.hs
--- a/src/haskell/lib/Arion/Services.hs
+++ b/src/haskell/lib/Arion/Services.hs
@@ -1,20 +1,17 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE CPP #-}
-module Arion.Services
-  ( getDefaultExec
-  ) where
 
-import Prelude()
-import Protolude hiding (to)
+module Arion.Services
+  ( getDefaultExec,
+  )
+where
 
 import qualified Data.Aeson as Aeson
+import Protolude hiding (to)
+import Prelude ()
 #if MIN_VERSION_lens_aeson(1,2,0)
 import qualified Data.Aeson.Key as AK
 #endif
-import           Arion.Aeson (decodeFile)
-
+import Arion.Aeson (decodeFile)
 import Control.Lens
 import Data.Aeson.Lens
 
@@ -31,7 +28,6 @@
 -- | Subject to change
 getDefaultExec :: FilePath -> Text -> IO [Text]
 getDefaultExec fp service = do
-
   v <- decodeFile fp
 
   pure ((v :: Aeson.Value) ^.. key "x-arion" . key "serviceInfo" . key (mkKey service) . key "defaultExec" . _Array . traverse . _String)
diff --git a/src/haskell/test/Arion/NixSpec.hs b/src/haskell/test/Arion/NixSpec.hs
--- a/src/haskell/test/Arion/NixSpec.hs
+++ b/src/haskell/test/Arion/NixSpec.hs
@@ -1,52 +1,75 @@
-{-# LANGUAGE OverloadedStrings #-}
 module Arion.NixSpec
-  ( spec
+  ( spec,
   )
 where
 
-import           Protolude
-import           Test.Hspec
-import qualified Data.List.NonEmpty            as NEL
-import           Arion.Aeson
-import           Arion.Nix
-import qualified Data.Text                     as T
-import qualified Data.Text.IO                  as T
+import Arion.Aeson
+import Arion.Nix
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Protolude
+import Test.Hspec
 
 spec :: Spec
-spec = describe "evaluateComposition" $ it "matches an example" $ do
-  x <- Arion.Nix.evaluateComposition EvaluationArgs
-    { evalUid      = 123
-    , evalModules  = NEL.fromList
-                       ["src/haskell/testdata/Arion/NixSpec/arion-compose.nix"]
-    , evalPkgs     = "import <nixpkgs> { system = \"x86_64-linux\"; }"
-    , evalWorkDir  = Nothing
-    , evalMode     = ReadOnly
-    , evalUserArgs = ["--show-trace"]
-    }
-  let actual = pretty x
-  expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
-  censorPaths actual `shouldBe` censorPaths expected
+spec = describe "evaluateComposition" $ do
+  it "matches an example" $ do
+    x <-
+      Arion.Nix.evaluateComposition
+        EvaluationArgs
+          { posixUID = 123,
+            evalModulesFile =
+              NEL.fromList
+                ["src/haskell/testdata/Arion/NixSpec/arion-compose.nix"],
+            pkgsExpr = "import <nixpkgs> { system = \"x86_64-linux\"; }",
+            workDir = Nothing,
+            mode = ReadOnly,
+            extraNixArgs = ["--show-trace"]
+          }
+    let actual = pretty x
+    expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
+    censorPaths actual `shouldBe` censorPaths expected
 
+  it "matches an build.context example" $ do
+    x <-
+      Arion.Nix.evaluateComposition
+        EvaluationArgs
+          { posixUID = 1234,
+            evalModulesFile =
+              NEL.fromList
+                ["src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix"],
+            pkgsExpr = "import <nixpkgs> { system = \"x86_64-linux\"; }",
+            workDir = Nothing,
+            mode = ReadOnly,
+            extraNixArgs = ["--show-trace"]
+          }
+    let actual = pretty x
+    expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-context-compose.json"
+    censorPaths actual `shouldBe` censorPaths expected
+
 censorPaths :: Text -> Text
 censorPaths = censorImages . censorStorePaths
 
 censorStorePaths :: Text -> Text
 censorStorePaths x = case T.breakOn "/nix/store/" x of
   (prefix, tl) | (tl :: Text) == "" -> prefix
-  (prefix, tl) -> prefix <> "<STOREPATH>" <> censorPaths
-    (T.dropWhile isNixNameChar $ T.drop (T.length "/nix/store/") tl)
+  (prefix, tl) ->
+    prefix
+      <> "<STOREPATH>"
+      <> censorPaths
+        (T.dropWhile isNixNameChar $ T.drop (T.length "/nix/store/") tl)
 
 -- Probably slow, due to not O(1) <>
 censorImages :: Text -> Text
 censorImages x = case T.break (\c -> c == ':' || c == '"') x of
   (prefix, tl) | tl == "" -> prefix
-  (prefix, tl) | let imageId = T.take 33 (T.drop 1 tl)
-               , T.last imageId == '\"'
-                 -- Approximation of nix hash validation
-               , T.all (\c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) (T.take 32 imageId)
-               -> prefix <> T.take 1 tl <> "<HASH>" <> censorImages (T.drop 33 tl)
+  (prefix, tl)
+    | let imageId = T.take 33 (T.drop 1 tl),
+      T.last imageId == '\"',
+      -- Approximation of nix hash validation
+      T.all (\c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) (T.take 32 imageId) ->
+        prefix <> T.take 1 tl <> "<HASH>" <> censorImages (T.drop 33 tl)
   (prefix, tl) -> prefix <> T.take 1 tl <> censorImages (T.drop 1 tl)
-
 
 -- | WARNING: THIS IS LIKELY WRONG: DON'T REUSE
 isNixNameChar :: Char -> Bool
diff --git a/src/haskell/test/Spec.hs b/src/haskell/test/Spec.hs
--- a/src/haskell/test/Spec.hs
+++ b/src/haskell/test/Spec.hs
@@ -1,10 +1,10 @@
 module Spec
-  ( spec
+  ( spec,
   )
 where
 
-import           Test.Hspec
 import qualified Arion.NixSpec
+import Test.Hspec
 
 spec :: Spec
 spec = do
diff --git a/src/haskell/test/TestMain.hs b/src/haskell/test/TestMain.hs
--- a/src/haskell/test/TestMain.hs
+++ b/src/haskell/test/TestMain.hs
@@ -1,10 +1,11 @@
 module Main where
 
-import           Prelude()
-import           Protolude
-import           Test.Hspec.Runner
+import Protolude
 import qualified Spec
+import Test.Hspec.Runner
+import Prelude ()
 
 main :: IO ()
 main = hspecWith config Spec.spec
-  where config = defaultConfig { configColorMode = ColorAlways }
+  where
+    config = defaultConfig {configColorMode = ColorAlways}
diff --git a/src/haskell/testdata/Arion/NixSpec/arion-compose.nix b/src/haskell/testdata/Arion/NixSpec/arion-compose.nix
--- a/src/haskell/testdata/Arion/NixSpec/arion-compose.nix
+++ b/src/haskell/testdata/Arion/NixSpec/arion-compose.nix
@@ -2,7 +2,7 @@
   project.name = "unit-test-data";
   services.webserver = { pkgs, ... }: {
     nixos.useSystemd = true;
-    nixos.configuration.boot.tmpOnTmpfs = true;
+    nixos.configuration.boot.tmp.useTmpfs = true;
     nixos.configuration.services.nginx.enable = true;
     nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
     service.useHostStore = true;
diff --git a/src/haskell/testdata/Arion/NixSpec/arion-context-compose.json b/src/haskell/testdata/Arion/NixSpec/arion-context-compose.json
new file mode 100644
--- /dev/null
+++ b/src/haskell/testdata/Arion/NixSpec/arion-context-compose.json
@@ -0,0 +1,41 @@
+{
+    "networks": {
+        "default": {
+            "name": "unit-test-data"
+        }
+    },
+    "services": {
+        "webserver": {
+            "build": {
+                "context": "<STOREPATH>"
+            },
+            "environment": {},
+            "ports": [
+                "8080:80"
+            ],
+            "sysctls": {},
+            "volumes": []
+        }
+    },
+    "version": "3.4",
+    "volumes": {},
+    "x-arion": {
+        "images": [
+            {
+                "imageExe": "<STOREPATH>",
+                "imageName": "localhost/webserver",
+                "imageTag": "<HASH>"
+            }
+        ],
+        "project": {
+            "name": "unit-test-data"
+        },
+        "serviceInfo": {
+            "webserver": {
+                "defaultExec": [
+                    "/bin/sh"
+                ]
+            }
+        }
+    }
+}
diff --git a/src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix b/src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix
new file mode 100644
--- /dev/null
+++ b/src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix
@@ -0,0 +1,9 @@
+{
+  project.name = "unit-test-data";
+  services.webserver.service = {
+    build.context = "${./build-context}";
+    ports = [
+      "8080:80"
+    ];
+  };
+}
diff --git a/src/nix/lib.nix b/src/nix/lib.nix
--- a/src/nix/lib.nix
+++ b/src/nix/lib.nix
@@ -3,11 +3,13 @@
 
   link = url: text: ''[${text}](${url})'';
 
+  composeSpecRev = "55b450aee50799a2f33cc99e1d714518babe305e";
+
   serviceRef = fragment:
-    ''See ${link "https://docs.docker.com/compose/compose-file/05-services/#${fragment}" "Docker Compose Services #${fragment}"}'';
+    ''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/05-services.md#${fragment}" "Compose Spec Services #${fragment}"}'';
 
   networkRef = fragment:
-    ''See ${link "https://docs.docker.com/compose/compose-file/06-networks/#${fragment}" "Docker Compose Network #${fragment}"}'';
+    ''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/06-networks.md#${fragment}" "Compose Spec Networks #${fragment}"}'';
 
 in
 {
diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix
--- a/src/nix/modules/service/docker-compose-service.nix
+++ b/src/nix/modules/service/docker-compose-service.nix
@@ -66,6 +66,22 @@
         https://docs.docker.com/compose/compose-file/build/#context
       '';
     };
+    service.build.dockerfile = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        Sets an alternate Dockerfile. A relative path is resolved from the build context.
+        https://docs.docker.com/compose/compose-file/build/#dockerfile
+      '';
+    };
+    service.build.target = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        Defines the stage to build as defined inside a multi-stage Dockerfile.
+        https://docs.docker.com/compose/compose-file/build/#target
+      '';
+    };
     service.hostname = mkOption {
       type = nullOr str;
       default = null;
@@ -86,7 +102,8 @@
       description = serviceRef "environment";
     };
     service.image = mkOption {
-      type = str;
+      type = nullOr str;
+      default = null;
       description = serviceRef "image";
     };
     service.command = mkOption {
@@ -296,6 +313,11 @@
       default = null;
       description = serviceRef "stop_signal";
     };
+    service.stop_grace_period = mkOption {
+      type = nullOr str;
+      default = null;
+      description = serviceRef "stop_grace_period";
+    };
     service.sysctls = mkOption {
       type = attrsOf (either str int);
       default = {};
@@ -321,6 +343,103 @@
         ${serviceRef "cap_drop"}
       '';
     };
+    service.blkio_config = mkOption {
+      default = null;
+      example = { weight = 300; };
+      description = ''
+        Block IO limits for the service.
+
+        ${serviceRef "blkio_config"}
+      '';
+      type = nullOr (
+        submodule (
+          { config, options, ... }:
+          let
+            deviceBpsModule = submodule (
+              { config, options, ... }:
+              {
+                options = {
+                  path = mkOption {
+                    type = str;
+                    example = "/dev/sda";
+                    description = serviceRef "blkio_config";
+                  };
+                  rate = mkOption {
+                    type = str;
+                    example = "12mb";
+                    description = serviceRef "blkio_config";
+                  };
+                };
+              }
+            );
+            deviceIopsModule = submodule (
+              { config, options, ... }:
+              {
+                options = {
+                  path = mkOption {
+                    type = str;
+                    example = "/dev/sda";
+                    description = serviceRef "blkio_config";
+                  };
+                  rate = mkOption {
+                    type = int;
+                    example = 120;
+                    description = serviceRef "blkio_config";
+                  };
+                };
+              }
+            );
+          in
+          {
+            options = {
+              _out = mkOption {
+                internal = true;
+                readOnly = true;
+                default = lib.mapAttrs (k: opt: opt.value) (lib.filterAttrs (_: opt: opt.isDefined) { inherit (options) weight weight_device device_read_bps device_write_bps device_read_iops device_write_iops; });
+              };
+              weight = mkOption {
+                type = int;
+                example = 300;
+                description = serviceRef "blkio_config";
+              };
+              weight_device = mkOption {
+                description = serviceRef "blkio_config";
+                type = listOf (submodule ({ config, options, ... }: {
+                  options = {
+                    path = mkOption {
+                      type = str;
+                      example = "/dev/sda";
+                      description = serviceRef "blkio_config";
+                    };
+                    weight = mkOption {
+                      type = int;
+                      example = 400;
+                      description = serviceRef "blkio_config";
+                    };
+                  };
+                }));
+              };
+              device_read_bps = mkOption {
+                type = listOf (deviceBpsModule);
+                description = serviceRef "blkio_config";
+              };
+              device_write_bps = mkOption {
+                type = listOf (deviceBpsModule);
+                description = serviceRef "blkio_config";
+              };
+              device_read_iops = mkOption {
+                type = listOf (deviceIopsModule);
+                description = serviceRef "blkio_config";
+              };
+              device_write_iops = mkOption {
+                type = listOf (deviceIopsModule);
+                description = serviceRef "blkio_config";
+              };
+            };
+          }
+        )
+      );
+    };
   };
 
   config.out.service = {
@@ -328,10 +447,11 @@
       volumes
       environment
       sysctls
-      image
       ;
-  } // lib.optionalAttrs (config.service.build.context != null) {
-    inherit (config.service) build;
+  } // lib.optionalAttrs (config.service.image != null) {
+    inherit (config.service) image;
+  } // lib.optionalAttrs (config.service.build.context != null ) {
+    build = lib.filterAttrs (n: v: v != null)  config.service.build;
   } // lib.optionalAttrs (cap_add != []) {
     inherit cap_add;
   } // lib.optionalAttrs (cap_drop != []) {
@@ -378,6 +498,8 @@
     inherit (config.service) restart;
   } // lib.optionalAttrs (config.service.stop_signal != null) {
     inherit (config.service) stop_signal;
+  } // lib.optionalAttrs (config.service.stop_grace_period != null) {
+    inherit (config.service) stop_grace_period;
   } // lib.optionalAttrs (config.service.tmpfs != []) {
     inherit (config.service) tmpfs;
   } // lib.optionalAttrs (config.service.tty != null) {
@@ -386,5 +508,7 @@
     inherit (config.service) working_dir;
   } // lib.optionalAttrs (config.service.user != null) {
     inherit (config.service) user;
+  } // lib.optionalAttrs (config.service.blkio_config != null) {
+    blkio_config = config.service.blkio_config._out;
   };
 }
diff --git a/src/nix/modules/service/image.nix b/src/nix/modules/service/image.nix
--- a/src/nix/modules/service/image.nix
+++ b/src/nix/modules/service/image.nix
@@ -163,17 +163,19 @@
       '';
     };
   };
-  config = {
-    build.image = builtImage;
-    build.imageName = config.build.image.imageName;
-    build.imageTag =
-                 if config.build.image.imageTag != ""
-                 then config.build.image.imageTag
-                 else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath));
-
-    service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}";
-    image.rawConfig.Cmd = config.image.command;
-
-    image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image);
-  };
+  config = lib.mkMerge [{
+      build.image = builtImage;
+      build.imageName = config.build.image.imageName;
+      build.imageTag =
+                   if config.build.image.imageTag != ""
+                   then config.build.image.imageTag
+                   else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath));
+      image.rawConfig.Cmd = config.image.command;
+      image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image);
+    }
+    ( lib.mkIf (config.service.build.context == null)
+    {
+      service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}";
+    })
+  ];
 }
diff --git a/src/nix/modules/service/nixos-init.nix b/src/nix/modules/service/nixos-init.nix
--- a/src/nix/modules/service/nixos-init.nix
+++ b/src/nix/modules/service/nixos-init.nix
@@ -39,7 +39,7 @@
     service.tmpfs = [
       "/run"          # noexec is fine because exes should be symlinked from elsewhere anyway
       "/run/wrappers" # noexec breaks this intentionally
-    ] ++ lib.optional (config.nixos.evaluatedConfig.boot.tmpOnTmpfs) "/tmp:exec,mode=777";
+    ] ++ lib.optional (config.nixos.evaluatedConfig.boot.tmp.useTmpfs) "/tmp:exec,mode=777";
 
     service.stop_signal = "SIGRTMIN+3";
     service.tty = true;
