diff --git a/Devel.hs b/Devel.hs
--- a/Devel.hs
+++ b/Devel.hs
@@ -66,17 +66,11 @@
 import qualified Config                                as GHC
 import           Data.Streaming.Network                (bindPortTCP)
 import           Network                               (withSocketsDo)
-#if MIN_VERSION_http_conduit(2, 0, 0)
 import           Network.HTTP.Conduit                  (conduitManagerSettings, newManager)
 import           Data.Default.Class                    (def)
-#else
-import           Network.HTTP.Conduit                  (def, newManager)
-#endif
 import           Network.HTTP.ReverseProxy             (ProxyDest (ProxyDest),
                                                         waiProxyToSettings, wpsTimeout, wpsOnExc)
-#if MIN_VERSION_http_reverse_proxy(0, 2, 0)
 import qualified Network.HTTP.ReverseProxy             as ReverseProxy
-#endif
 import           Network.HTTP.Types                    (status200, status503)
 import           Network.Socket                        (sClose)
 import           Network.Wai                           (responseLBS, requestHeaders)
@@ -130,11 +124,7 @@
 -- 3001, give an appropriate message to the user.
 reverseProxy :: DevelOpts -> I.IORef Int -> IO ()
 reverseProxy opts iappPort = do
-#if MIN_VERSION_http_conduit(2, 0, 0)
     manager <- newManager conduitManagerSettings
-#else
-    manager <- newManager def
-#endif
     let refreshHtml = LB.fromChunks $ return $(embedFile "refreshing.html")
     let onExc _ req
             | maybe False (("application/json" `elem`) . parseHttpAccept)
@@ -154,18 +144,10 @@
                 (const $ do
                     appPort <- liftIO $ I.readIORef iappPort
                     return $
-#if MIN_VERSION_http_reverse_proxy(0, 2, 0)
                         ReverseProxy.WPRProxyDest
-#else
-                        Right
-#endif
                         $ ProxyDest "127.0.0.1" appPort)
                 def
-#if MIN_VERSION_wai(3, 0, 0)
                     { wpsOnExc = \e req f -> onExc e req >>= f
-#else
-                    { wpsOnExc = onExc
-#endif
                     , wpsTimeout =
                         if proxyTimeout opts == 0
                             then Nothing
diff --git a/Keter.hs b/Keter.hs
--- a/Keter.hs
+++ b/Keter.hs
@@ -17,6 +17,8 @@
 import Control.Exception
 import qualified Data.ByteString.Lazy as L
 import Codec.Compression.GZip (compress)
+import qualified Data.Foldable as Fold
+import Control.Monad.Trans.Writer (tell, execWriter)
 
 run :: String -> [String] -> IO ()
 run a b = do
@@ -36,7 +38,12 @@
                 case Map.lookup "host" value of
                     Just (String s) | "<<" `T.isPrefixOf` s ->
                         error $ "Please set your hostname in " ++ ketercfg
-                    _ -> return value
+                    _ ->
+                        case Map.lookup "user-edited" value of
+                            Just (Bool False) ->
+                                error $ "Please edit your Keter config file at "
+                                     ++ ketercfg
+                            _ -> return value
             Just _ -> error $ ketercfg ++ " is not an object"
 
     files <- getDirectoryContents "."
@@ -46,10 +53,14 @@
             [] -> error "No cabal file found"
             _ -> error "Too many cabal files found"
 
-    exec <-
-        case Map.lookup "exec" value of
-            Just (String s) -> return $ F.collapse $ "config" F.</> F.fromText s
-            _ -> error $ "exec not found in " ++ ketercfg
+    let findExecs (Object v) =
+            mapM_ go $ Map.toList v
+          where
+            go ("exec", String s) = tell [F.collapse $ "config" F.</> F.fromText s]
+            go (_, v') = findExecs v'
+        findExecs (Array v) = Fold.mapM_ findExecs v
+        findExecs _ = return ()
+        execs = execWriter $ findExecs $ Object value
 
     unless noBuild $ do
         run cabal ["clean"]
@@ -58,7 +69,7 @@
 
     _ <- try' $ F.removeTree "static/tmp"
 
-    archive <- Tar.pack "" [F.encodeString exec, "config", "static"]
+    archive <- Tar.pack "" $ "config" : "static" : map F.encodeString execs
     let fp = T.unpack project ++ ".keter"
     L.writeFile fp $ compress $ Tar.write archive
 
diff --git a/Options.hs b/Options.hs
--- a/Options.hs
+++ b/Options.hs
@@ -76,15 +76,9 @@
      p <|> if any ((==Just "1") . getEnvValue env path) names then pure a else empty
   | otherwise = p
   where
-#if MIN_VERSION_optparse_applicative(0,6,0)
     right= ReadM . Right
     left = ReadM . Left
     either' f g (ReadM x) = either f g x
-#else
-    right = Right
-    left = Left
-    either' = either
-#endif
 injectDefaultP env path (MultP p1 p2) =
    MultP (injectDefaultP env path p1) (injectDefaultP env path p2)
 injectDefaultP env path (AltP p1 p2) =
diff --git a/ghcwrapper.hs b/ghcwrapper.hs
--- a/ghcwrapper.hs
+++ b/ghcwrapper.hs
@@ -52,6 +52,7 @@
       return (ExitFailure 1)
     Just p -> rawSystem (programPath p) args
 
+main :: IO ()
 main = do
   args <- getArgs
   e <- doesDirectoryExist "yesod-devel"
diff --git a/hsfiles/mongo.hsfiles b/hsfiles/mongo.hsfiles
--- a/hsfiles/mongo.hsfiles
+++ b/hsfiles/mongo.hsfiles
@@ -96,7 +96,14 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s p manager dbconf logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , connPool = p
+            , httpManager = manager
+            , persistConfig = dbconf
+            , appLogger = logger
+            }
 
     return foundation
 
@@ -256,6 +263,8 @@
 
     authHttpManager = httpManager
 
+instance YesodAuthPersist App
+
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
 instance RenderMessage App FormMessage where
@@ -408,16 +417,16 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
-                 , persistent                    >= 2.0        && < 2.1
-                 , persistent-mongoDB            >= 2.0        && < 2.1
-                 , persistent-template           >= 2.0        && < 2.1
+                 , persistent                    >= 2.0        && < 2.2
+                 , persistent-mongoDB            >= 2.0        && < 2.2
+                 , persistent-template           >= 2.0        && < 2.2
                  , template-haskell
                  , shakespeare                   >= 2.0        && < 2.1
                  , hjsmin                        >= 0.1        && < 0.2
@@ -453,12 +462,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , persistent
@@ -705,15 +714,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/models #-}
 User
     ident Text
@@ -7973,7 +8030,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -7995,7 +8052,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -8013,7 +8070,7 @@
             users <- runDB $ selectList ([] :: [Filter User]) []
             assertEqual "user table empty" 0 $ L.length users
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -8043,7 +8100,7 @@
     pool <- fmap connPool getTestYesod
     liftIO $ runResourceT $ runNoLoggingT $ runMongoDBPoolDef query pool
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/hsfiles/mysql.hsfiles b/hsfiles/mysql.hsfiles
--- a/hsfiles/mysql.hsfiles
+++ b/hsfiles/mysql.hsfiles
@@ -98,7 +98,14 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s p manager dbconf logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , connPool = p
+            , httpManager = manager
+            , persistConfig = dbconf
+            , appLogger = logger
+            }
 
     -- Perform database migration using our application's logging settings.
     runLoggingT
@@ -265,6 +272,8 @@
 
     authHttpManager = httpManager
 
+instance YesodAuthPersist App
+
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
 instance RenderMessage App FormMessage where
@@ -414,16 +423,16 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
-                 , persistent                    >= 2.0        && < 2.1
-                 , persistent-mysql              >= 2.0        && < 2.1
-                 , persistent-template           >= 2.0        && < 2.1
+                 , persistent                    >= 2.0        && < 2.2
+                 , persistent-mysql              >= 2.0        && < 2.2
+                 , persistent-template           >= 2.0        && < 2.2
                  , template-haskell
                  , shakespeare                   >= 2.0        && < 2.1
                  , hjsmin                        >= 0.1        && < 0.2
@@ -459,12 +468,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , persistent
@@ -711,15 +720,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/models #-}
 User
     ident Text
@@ -735,37 +792,20 @@
  -- By default this file is used in Model.hs (which is imported by Foundation.hs)
 
 {-# START_FILE config/mysql.yml #-}
-Default: &defaults
-  user: PROJECTNAME
-  password: PROJECTNAME
-  host: localhost
-  port: 3306
-  database: PROJECTNAME
-  poolsize: 10
-
-Development:
-  <<: *defaults
-
-Testing:
-  database: PROJECTNAME_test
-  <<: *defaults
-
-Staging:
-  database: PROJECTNAME_staging
-  poolsize: 100
-  <<: *defaults
-
-Production:
-  database: PROJECTNAME_production
-  poolsize: 100
-  <<: *defaults
+# NOTE: These settings can be overridden by environment variables as well, in
+# particular:
+#
+#    MYSQL_HOST
+#    MYSQL_PORT
+#    MYSQL_USER
+#    MYSQL_PASSWORD
+#    MYSQL_DATABASE
 
-{-# START_FILE config/postgresql.yml #-}
 Default: &defaults
   user: PROJECTNAME
   password: PROJECTNAME
   host: localhost
-  port: 5432
+  port: 3306
   database: PROJECTNAME
   poolsize: 10
 
@@ -8005,7 +8045,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -8027,7 +8067,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -8045,7 +8085,7 @@
             users <- runDB $ selectList ([] :: [Filter User]) []
             assertEqual "user table empty" 0 $ L.length users
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -8073,7 +8113,7 @@
     pool <- fmap connPool getTestYesod
     liftIO $ runSqlPersistMPool query pool
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/hsfiles/postgres-fay.hsfiles b/hsfiles/postgres-fay.hsfiles
--- a/hsfiles/postgres-fay.hsfiles
+++ b/hsfiles/postgres-fay.hsfiles
@@ -101,7 +101,15 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s p manager dbconf onCommand logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , connPool = p
+            , httpManager = manager
+            , persistConfig = dbconf
+            , fayCommandHandler = onCommand
+            , appLogger = logger
+            }
 
     -- Perform database migration using our application's logging settings.
     runLoggingT
@@ -278,6 +286,8 @@
 
     authHttpManager = httpManager
 
+instance YesodAuthPersist App
+
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
 instance RenderMessage App FormMessage where
@@ -450,18 +460,18 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , yesod-fay                     >= 0.5.0.1
                  , fay                           >= 0.16
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
-                 , persistent                    >= 2.0        && < 2.1
-                 , persistent-postgresql         >= 2.0        && < 2.1
-                 , persistent-template           >= 2.0        && < 2.1
+                 , persistent                    >= 2.0        && < 2.2
+                 , persistent-postgresql         >= 2.0        && < 2.2
+                 , persistent-template           >= 2.0        && < 2.2
                  , template-haskell
                  , shakespeare                   >= 2.0        && < 2.1
                  , monad-control                 >= 0.3        && < 0.4
@@ -496,12 +506,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , persistent
@@ -760,15 +770,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/models #-}
 User
     ident Text
@@ -784,6 +842,15 @@
  -- By default this file is used in Model.hs (which is imported by Foundation.hs)
 
 {-# START_FILE config/postgresql.yml #-}
+# NOTE: These settings can be overridden by environment variables as well, in
+# particular:
+#
+#    PGHOST
+#    PGPORT
+#    PGUSER
+#    PGPASS
+#    PGDATABASE
+
 Default: &defaults
   user: PROJECTNAME
   password: PROJECTNAME
@@ -8091,7 +8158,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -8113,7 +8180,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -8131,7 +8198,7 @@
             users <- runDB $ selectList ([] :: [Filter User]) []
             assertEqual "user table empty" 0 $ L.length users
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -8159,7 +8226,7 @@
     pool <- fmap connPool getTestYesod
     liftIO $ runSqlPersistMPool query pool
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/hsfiles/postgres.hsfiles b/hsfiles/postgres.hsfiles
--- a/hsfiles/postgres.hsfiles
+++ b/hsfiles/postgres.hsfiles
@@ -98,7 +98,14 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s p manager dbconf logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , connPool = p
+            , httpManager = manager
+            , persistConfig = dbconf
+            , appLogger = logger
+            }
 
     -- Perform database migration using our application's logging settings.
     runLoggingT
@@ -265,6 +272,8 @@
 
     authHttpManager = httpManager
 
+instance YesodAuthPersist App
+
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
 instance RenderMessage App FormMessage where
@@ -414,16 +423,16 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
-                 , persistent                    >= 2.0        && < 2.1
-                 , persistent-postgresql         >= 2.0        && < 2.1
-                 , persistent-template           >= 2.0        && < 2.1
+                 , persistent                    >= 2.0        && < 2.2
+                 , persistent-postgresql         >= 2.0        && < 2.2
+                 , persistent-template           >= 2.0        && < 2.2
                  , template-haskell
                  , shakespeare                   >= 2.0        && < 2.1
                  , hjsmin                        >= 0.1        && < 0.2
@@ -459,12 +468,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , persistent
@@ -711,15 +720,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/models #-}
 User
     ident Text
@@ -735,6 +792,15 @@
  -- By default this file is used in Model.hs (which is imported by Foundation.hs)
 
 {-# START_FILE config/postgresql.yml #-}
+# NOTE: These settings can be overridden by environment variables as well, in
+# particular:
+#
+#    PGHOST
+#    PGPORT
+#    PGUSER
+#    PGPASS
+#    PGDATABASE
+
 Default: &defaults
   user: PROJECTNAME
   password: PROJECTNAME
@@ -7979,7 +8045,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -8001,7 +8067,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -8019,7 +8085,7 @@
             users <- runDB $ selectList ([] :: [Filter User]) []
             assertEqual "user table empty" 0 $ L.length users
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -8047,7 +8113,7 @@
     pool <- fmap connPool getTestYesod
     liftIO $ runSqlPersistMPool query pool
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/hsfiles/simple.hsfiles b/hsfiles/simple.hsfiles
--- a/hsfiles/simple.hsfiles
+++ b/hsfiles/simple.hsfiles
@@ -89,7 +89,12 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s manager logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , httpManager = manager
+            , appLogger = logger
+            }
 
     return foundation
 
@@ -341,11 +346,11 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
                  , template-haskell
@@ -383,12 +388,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , hspec
@@ -626,15 +631,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/robots.txt #-}
 User-agent: *
 
@@ -7853,7 +7906,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -7874,7 +7927,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -7883,7 +7936,7 @@
             htmlAllContain ".message" "Some Content"
             htmlAllContain ".message" "text/plain"
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -7898,7 +7951,7 @@
 type Spec = YesodSpec App
 type Example = YesodExample App
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/hsfiles/sqlite.hsfiles b/hsfiles/sqlite.hsfiles
--- a/hsfiles/sqlite.hsfiles
+++ b/hsfiles/sqlite.hsfiles
@@ -98,7 +98,14 @@
     (getter, _) <- clockDateCacher
 
     let logger = Yesod.Core.Types.Logger loggerSet' getter
-        foundation = App conf s p manager dbconf logger
+        foundation = App
+            { settings = conf
+            , getStatic = s
+            , connPool = p
+            , httpManager = manager
+            , persistConfig = dbconf
+            , appLogger = logger
+            }
 
     -- Perform database migration using our application's logging settings.
     runLoggingT
@@ -265,6 +272,8 @@
 
     authHttpManager = httpManager
 
+instance YesodAuthPersist App
+
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
 instance RenderMessage App FormMessage where
@@ -414,16 +423,16 @@
                 DeriveDataTypeable
 
     build-depends: base                          >= 4          && < 5
-                 , yesod                         >= 1.2.5      && < 1.3
-                 , yesod-core                    >= 1.2.20     && < 1.3
-                 , yesod-auth                    >= 1.3        && < 1.4
-                 , yesod-static                  >= 1.2        && < 1.3
-                 , yesod-form                    >= 1.3        && < 1.4
+                 , yesod                         >= 1.4.0      && < 1.5
+                 , yesod-core                    >= 1.4.0      && < 1.5
+                 , yesod-auth                    >= 1.4.0      && < 1.5
+                 , yesod-static                  >= 1.4.0      && < 1.5
+                 , yesod-form                    >= 1.4.0      && < 1.5
                  , bytestring                    >= 0.9        && < 0.11
                  , text                          >= 0.11       && < 2.0
-                 , persistent                    >= 2.0        && < 2.1
-                 , persistent-sqlite             >= 2.0        && < 2.1
-                 , persistent-template           >= 2.0        && < 2.1
+                 , persistent                    >= 2.0        && < 2.2
+                 , persistent-sqlite             >= 2.0        && < 2.2
+                 , persistent-template           >= 2.0        && < 2.2
                  , template-haskell
                  , shakespeare                   >= 2.0        && < 2.1
                  , hjsmin                        >= 0.1        && < 0.2
@@ -459,12 +468,12 @@
 test-suite test
     type:              exitcode-stdio-1.0
     main-is:           main.hs
-    hs-source-dirs:    tests
+    hs-source-dirs:    test
     ghc-options:       -Wall
 
     build-depends: base
                  , PROJECTNAME
-                 , yesod-test >= 1.2 && < 1.3
+                 , yesod-test >= 1.4 && < 1.5
                  , yesod-core
                  , yesod
                  , persistent
@@ -711,15 +720,63 @@
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
 
 {-# START_FILE config/keter.yml #-}
-exec: ../dist/build/PROJECTNAME/PROJECTNAME
-args:
-    - production
-host: <<HOST-NOT-SET>>
+# After you've edited this file, remove the following line to allow
+# `yesod keter` to build your bundle.
+user-edited: false
 
+# A Keter app is composed of 1 or more stanzas. The main stanza will define our
+# web application. See the Keter documentation for more information on
+# available stanzas.
+stanzas:
+
+  # Your Yesod application.
+  - type: webapp
+
+    # Name of your executable. You are unlikely to need to change this.
+    # Note that all file paths are relative to the keter.yml file.
+    exec: ../dist/build/PROJECTNAME/PROJECTNAME
+
+    # Command line options passed to your application.
+    args:
+      - production
+
+    hosts:
+      # You can specify one or more hostnames for your application to respond
+      # to. The primary hostname will be used for generating your application
+      # root.
+      - www.PROJECTNAME.com
+
+  # Static files.
+  - type: static-files
+    hosts:
+      - static.PROJECTNAME.com
+    root: ../static
+
+    # Uncomment to turn on directory listings.
+    # directory-listing: true
+
+  # Redirect plain domain name to www.
+  - type: redirect
+
+    hosts:
+      - PROJECTNAME.com
+    actions:
+      - host: www.PROJECTNAME.com
+        # secure: false
+        # port: 80
+
+    # Uncomment to switch to a non-permanent redirect.
+    # status: 303
+
 # Use the following to automatically copy your bundle upon creation via `yesod
 # keter`. Uses `scp` internally, so you can set it to a remote destination
 # copy-to: user@host:/opt/keter/incoming
 
+# If you would like to have Keter automatically create a PostgreSQL database
+# and set appropriate environment variables for it to be discovered, uncomment
+# the following line.
+# postgres: true
+
 {-# START_FILE config/models #-}
 User
     ident Text
@@ -7975,7 +8032,7 @@
     color: #990
 }
 
-{-# START_FILE tests/HomeTest.hs #-}
+{-# START_FILE test/HomeTest.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module HomeTest
     ( homeSpecs
@@ -7997,7 +8054,7 @@
                 setMethod "POST"
                 setUrl HomeR
                 addNonce
-                fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference
+                fileByLabel "Choose a file" "test/main.hs" "text/plain" -- talk about self-reference
                 byLabel "What's on the file?" "Some Content"
 
             statusIs 200
@@ -8015,7 +8072,7 @@
             users <- runDB $ selectList ([] :: [Filter User]) []
             assertEqual "user table empty" 0 $ L.length users
 
-{-# START_FILE tests/TestImport.hs #-}
+{-# START_FILE test/TestImport.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 module TestImport
     ( module Yesod.Test
@@ -8043,7 +8100,7 @@
     pool <- fmap connPool getTestYesod
     liftIO $ runSqlPersistMPool query pool
 
-{-# START_FILE tests/main.hs #-}
+{-# START_FILE test/main.hs #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
diff --git a/main.hs b/main.hs
--- a/main.hs
+++ b/main.hs
@@ -15,11 +15,7 @@
 import qualified Paths_yesod_bin
 import           Scaffolding.Scaffolder
 
-#if MIN_VERSION_optparse_applicative(0,6,0)
 import           Options.Applicative.Types (ReadM (ReadM))
-#else
-import           Options.Applicative.Builder.Internal (Mod, OptionFields)
-#endif
 
 import           HsFile                 (mkHsFile)
 #ifndef WINDOWS
@@ -155,12 +151,6 @@
 defaultRescan :: Int
 defaultRescan = 10
 
-#if MIN_VERSION_optparse_applicative(0,10,0)
-option' = option auto
-#else
-option' = option
-#endif
-
 develOptions :: Parser Command
 develOptions = Devel <$> switch ( long "disable-api"  <> short 'd'
                             <> help "Disable fast GHC API rebuilding")
@@ -168,7 +158,7 @@
                             <> help "Run COMMAND after rebuild succeeds")
                      <*> optStr ( long "failure-hook" <> short 'f' <> metavar "COMMAND"
                             <> help "Run COMMAND when rebuild fails")
-                     <*> option' ( long "event-timeout" <> short 't' <> value defaultRescan <> metavar "N"
+                     <*> option auto ( long "event-timeout" <> short 't' <> value defaultRescan <> metavar "N"
                             <> help ("Force rescan of files every N seconds (default "
                                      ++ show defaultRescan
                                      ++ ", use -1 to rely on FSNotify alone)") )
@@ -178,9 +168,9 @@
                                    <> help "ignore file changes in DIR" )
                               )
                      <*> extraCabalArgs
-                     <*> option' ( long "port" <> short 'p' <> value 3000 <> metavar "N"
+                     <*> option auto ( long "port" <> short 'p' <> value 3000 <> metavar "N"
                             <> help "Devel server listening port" )
-                     <*> option' ( long "proxy-timeout" <> short 'x' <> value 0 <> metavar "N"
+                     <*> option auto ( long "proxy-timeout" <> short 'x' <> value 0 <> metavar "N"
                             <> help "Devel server timeout before returning 'not ready' message (in seconds, 0 for none)" )
                      <*> switch ( long "disable-reverse-proxy" <> short 'n'
                             <> help "Disable reverse proxy" )
@@ -195,17 +185,9 @@
 -- | Optional @String@ argument
 optStr :: Mod OptionFields (Maybe String) -> Parser (Maybe String)
 optStr m =
-#if MIN_VERSION_optparse_applicative(0,10,0)
     nullOption (success . str) $ value Nothing <> m
-#else
-    nullOption $ value Nothing <> reader (success . str)  <> m
-#endif
   where
-#if MIN_VERSION_optparse_applicative(0,6,0)
     success = ReadM . Right
-#else
-    success = Right
-#endif
 
 -- | Like @rawSystem@, but exits if it receives a non-success result.
 rawSystem' :: String -> [String] -> IO ()
diff --git a/yesod-bin.cabal b/yesod-bin.cabal
--- a/yesod-bin.cabal
+++ b/yesod-bin.cabal
@@ -1,5 +1,5 @@
 name:            yesod-bin
-version:         1.2.13
+version:         1.4.0
 license:         MIT
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
@@ -51,11 +51,7 @@
                      , ghc-paths          >= 0.1
                      , parsec             >= 2.1          && < 4
                      , text               >= 0.11
-                     , shakespeare
-                     , shakespeare-text   >= 1.0
-                     , shakespeare        >= 1.0.2        && < 2.1
-                     , shakespeare-js     >= 1.0.2
-                     , shakespeare-css    >= 1.0.2
+                     , shakespeare        >= 2.0
                      , bytestring         >= 0.9.1.4
                      , time               >= 1.1.4
                      , template-haskell
@@ -74,19 +70,18 @@
                      , system-fileio      >= 0.3          && < 0.4
                      , unordered-containers
                      , yaml               >= 0.8          && < 0.9
-                     , optparse-applicative >= 0.5
+                     , optparse-applicative >= 0.10
                      , fsnotify           >= 0.0          && < 0.2
                      , split              >= 0.2          && < 0.3
                      , file-embed
-                     , conduit            >= 0.5          && < 1.3
+                     , conduit            >= 1.2
                      , conduit-extra
                      , resourcet          >= 0.3          && < 1.2
                      , base64-bytestring
                      , lifted-base
-                     , http-reverse-proxy >= 0.1.1
+                     , http-reverse-proxy >= 0.4
                      , network
-                     , http-conduit
-                     , network-conduit
+                     , http-conduit       >= 2.1.4
                      , project-template   >= 0.1.1
                      , transformers
                      , warp               >= 1.3.7.5
