diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.5.0.1...main)
+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.6.0.0...main)
+
+## [v1.6.0.0](https://github.com/freckle/stackctl/compare/v1.5.0.1...v1.6.0.0)
+
+- Re-implement `Stackctl.AWS` with `amazonka-mtl`.
+
+_No CLI or behavior changes._
 
 ## [v1.5.0.1](https://github.com/freckle/stackctl/compare/v1.5.0.0...v1.5.0.1)
 
diff --git a/src/Stackctl/AWS/CloudFormation.hs b/src/Stackctl/AWS/CloudFormation.hs
--- a/src/Stackctl/AWS/CloudFormation.hs
+++ b/src/Stackctl/AWS/CloudFormation.hs
@@ -86,6 +86,7 @@
   , _MatchServiceError
   , _ServiceError
   )
+import qualified Amazonka.Env as Amazonka
 import Amazonka.Waiter (Accept (..))
 import Conduit
 import Control.Lens ((?~))
@@ -97,7 +98,7 @@
 import Data.Time (UTCTime, defaultTimeLocale, formatTime, getCurrentTime)
 import qualified Data.UUID as UUID
 import qualified Data.UUID.V4 as UUID
-import Stackctl.AWS.Core
+import Stackctl.AWS.Core as AWS
 import Stackctl.Sort
 import Stackctl.StackDescription
 import System.FilePath.Glob
@@ -172,16 +173,16 @@
   pure $ ChangeSetName $ T.intercalate "-" $ map pack parts
 
 awsCloudFormationDescribeStack
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => StackName -> m Stack
+  :: (MonadIO m, MonadAWS m) => StackName -> m Stack
 awsCloudFormationDescribeStack stackName = do
   let req = newDescribeStacks & describeStacks_stackName ?~ unStackName stackName
 
-  awsSimple req $ \resp -> do
+  AWS.simple req $ \resp -> do
     stacks <- resp ^. describeStacksResponse_stacks
     listToMaybe stacks
 
 awsCloudFormationDescribeStackMaybe
-  :: (MonadUnliftIO m, MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadUnliftIO m, MonadAWS m)
   => StackName
   -> m (Maybe Stack)
 awsCloudFormationDescribeStackMaybe stackName =
@@ -193,7 +194,7 @@
     <$> awsCloudFormationDescribeStack stackName
 
 awsCloudFormationDescribeStackOutputs
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m)
   => StackName
   -> m [Output]
 awsCloudFormationDescribeStackOutputs stackName = do
@@ -201,7 +202,7 @@
   pure $ fromMaybe [] $ outputs stack
 
 awsCloudFormationDescribeStackEvents
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m)
   => StackName
   -> Maybe Text
   -- ^ Last-seen Id
@@ -213,21 +214,21 @@
           ?~ unStackName stackName
 
   runConduit
-    $ awsPaginate req
+    $ AWS.paginate req
     .| mapC (fromMaybe [] . (^. describeStackEventsResponse_stackEvents))
     .| concatC
     .| takeWhileC (\e -> Just (e ^. stackEvent_eventId) /= mLastId)
     .| sinkList
 
 awsCloudFormationGetStackNamesMatching
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m)
   => Pattern
   -> m [StackName]
 awsCloudFormationGetStackNamesMatching p = do
   let req = newListStacks & listStacks_stackStatusFilter ?~ runningStatuses
 
   runConduit
-    $ awsPaginate req
+    $ AWS.paginate req
     .| concatMapC (^. listStacksResponse_stackSummaries)
     .| concatC
     .| mapC (^. stackSummary_stackName)
@@ -236,7 +237,7 @@
     .| sinkList
 
 awsCloudFormationGetMostRecentStackEventId
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m)
   => StackName
   -> m (Maybe Text)
 awsCloudFormationGetMostRecentStackEventId stackName = do
@@ -252,14 +253,14 @@
       [] -> Nothing
       (e : _) -> Just $ e ^. stackEvent_eventId
 
-  awsSimple req
+  AWS.simple req
     $ pure
     . getFirstEventId
     . fromMaybe []
     . (^. describeStackEventsResponse_stackEvents)
 
 awsCloudFormationDeleteStack
-  :: (MonadResource m, MonadLogger m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadLogger m, MonadAWS m)
   => StackName
   -> m StackDeleteResult
 awsCloudFormationDeleteStack stackName = do
@@ -268,25 +269,25 @@
     describeReq =
       newDescribeStacks & describeStacks_stackName ?~ unStackName stackName
 
-  awsSimple deleteReq $ const $ pure ()
+  AWS.simple deleteReq $ const $ pure ()
 
   logDebug "Awaiting DeleteStack"
-  stackDeleteResult <$> awsAwait newStackDeleteComplete describeReq
+  stackDeleteResult <$> AWS.await newStackDeleteComplete describeReq
 
 awsCloudFormationWait
-  :: (MonadUnliftIO m, MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadUnliftIO m, MonadAWS m)
   => StackName
   -> m StackDeployResult
 awsCloudFormationWait stackName = do
   either stackCreateResult stackUpdateResult
     <$> race
-      (awsAwait newStackCreateComplete req)
-      (awsAwait newStackUpdateComplete req)
+      (AWS.await newStackCreateComplete req)
+      (AWS.await newStackUpdateComplete req)
  where
   req = newDescribeStacks & describeStacks_stackName ?~ unStackName stackName
 
 awsCloudFormationGetTemplate
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => StackName -> m Value
+  :: (MonadIO m, MonadAWS m) => StackName -> m Value
 awsCloudFormationGetTemplate stackName = do
   let
     req =
@@ -298,7 +299,7 @@
     decodeTemplateBody body =
       fromMaybe (toJSON body) $ decodeStrict $ encodeUtf8 body
 
-  awsSimple req $ \resp -> do
+  AWS.simple req $ \resp -> do
     body <- resp ^. getTemplateResponse_templateBody
     pure $ decodeTemplateBody body
 
@@ -366,10 +367,8 @@
 
 awsCloudFormationCreateChangeSet
   :: ( MonadUnliftIO m
-     , MonadResource m
      , MonadLogger m
-     , MonadReader env m
-     , HasAwsEnv env
+     , MonadAWS m
      )
   => StackName
   -> Maybe StackDescription
@@ -410,22 +409,22 @@
       logInfo
         $ "Creating changeset..."
         :# ["name" .= name, "type" .= changeSetType]
-      csId <- awsSimple req (^. createChangeSetResponse_id)
+      csId <- AWS.simple req (^. createChangeSetResponse_id)
 
       logDebug "Awaiting CREATE_COMPLETE"
-      void $ awsAwait newChangeSetCreateComplete $ newDescribeChangeSet csId
+      void $ AWS.await newChangeSetCreateComplete $ newDescribeChangeSet csId
 
       logInfo "Retrieving changeset..."
       cs <- awsCloudFormationDescribeChangeSet $ ChangeSetId csId
       pure $ cs <$ guard (not $ changeSetFailed cs)
 
 awsCloudFormationDescribeChangeSet
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m)
   => ChangeSetId
   -> m ChangeSet
 awsCloudFormationDescribeChangeSet changeSetId = do
   let req = newDescribeChangeSet $ unChangeSetId changeSetId
-  awsSimple req changeSetFromResponse
+  AWS.simple req changeSetFromResponse
 
 sortChanges :: [Change] -> [Change]
 sortChanges = sortByDependencies changeName changeCausedBy
@@ -445,18 +444,16 @@
   T.takeWhile (/= '.') <$> causingEntity
 
 awsCloudFormationExecuteChangeSet
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => ChangeSetId -> m ()
+  :: (MonadIO m, MonadAWS m) => ChangeSetId -> m ()
 awsCloudFormationExecuteChangeSet changeSetId = do
-  void $ awsSend $ newExecuteChangeSet $ unChangeSetId changeSetId
+  void $ AWS.send $ newExecuteChangeSet $ unChangeSetId changeSetId
 
 awsCloudFormationDeleteAllChangeSets
-  :: (MonadResource m, MonadLogger m, MonadReader env m, HasAwsEnv env)
-  => StackName
-  -> m ()
+  :: (MonadIO m, MonadLogger m, MonadAWS m) => StackName -> m ()
 awsCloudFormationDeleteAllChangeSets stackName = do
   logInfo "Deleting all changesets"
   runConduit
-    $ awsPaginate (newListChangeSets $ unStackName stackName)
+    $ AWS.paginate (newListChangeSets $ unStackName stackName)
     .| concatMapC
       ( \resp -> fromMaybe [] $ do
           ss <- resp ^. listChangeSetsResponse_summaries
@@ -465,7 +462,7 @@
     .| mapM_C
       ( \csId -> do
           logInfo $ "Enqueing delete" :# ["changeSetId" .= csId]
-          void $ awsSend $ newDeleteChangeSet csId
+          void $ AWS.send $ newDeleteChangeSet csId
       )
 
 -- | Did we abandoned this Stack's first ever ChangeSet?
@@ -509,3 +506,8 @@
 _ValidationError :: AsError a => Getting (First ServiceError) a ServiceError
 _ValidationError =
   _MatchServiceError defaultService "ValidationError" . hasStatus 400
+
+awsSilently :: MonadAWS m => m a -> m a
+awsSilently = AWS.localEnv $ Amazonka.env_logger .~ noop
+ where
+  noop _level _msg = pure ()
diff --git a/src/Stackctl/AWS/Core.hs b/src/Stackctl/AWS/Core.hs
--- a/src/Stackctl/AWS/Core.hs
+++ b/src/Stackctl/AWS/Core.hs
@@ -1,95 +1,76 @@
 module Stackctl.AWS.Core
-  ( AwsEnv
-  , HasAwsEnv (..)
-  , awsEnvDiscover
-  , awsWithAuth
-  , awsSimple
-  , awsSend
-  , awsPaginate
-  , awsAwait
-  , awsAssumeRole
-
-    -- * Modifiers on 'AwsEnv'
-  , awsWithin
-  , awsTimeout
-  , awsSilently
+  ( MonadAWS
+  , send
+  , paginate
+  , await
+  , withAuth
+  , localEnv
 
-    -- * 'Amazonka' extensions
-  , AccountId (..)
+    -- * "Control.Monad.AWS" extensions
+  , simple
+  , discover
+  , assumeRole
 
     -- * Error-handling
   , handlingServiceError
   , formatServiceError
 
-    -- * 'Amazonka'/'ResourceT' re-exports
+    -- * "Amazonka" extensions
+  , AccountId (..)
+
+    -- * "Amazonka" re-exports
   , Region (..)
   , FromText (..)
   , ToText (..)
-  , MonadResource
   ) where
 
-import Stackctl.Prelude hiding (timeout)
+import Stackctl.Prelude
 
-import Amazonka hiding (LogLevel (..))
-import qualified Amazonka as AWS
+import Amazonka
+  ( AWSRequest
+  , AWSResponse
+  , Region
+  , ServiceError
+  , serviceError_code
+  , serviceError_message
+  , serviceError_requestId
+  , _Sensitive
+  , _ServiceError
+  )
+import qualified Amazonka
 import Amazonka.Auth.Keys (fromSession)
 import Amazonka.Data.Text (FromText (..), ToText (..))
-import Amazonka.Env (env_auth, env_logger, env_region)
+import qualified Amazonka.Env as Amazonka
 import Amazonka.STS.AssumeRole
-import Conduit (ConduitM)
+import Control.Monad.AWS
 import Control.Monad.Logger (defaultLoc, toLogStr)
-import Control.Monad.Trans.Resource (MonadResource)
 import Data.Typeable (typeRep)
 import Stackctl.AWS.Orphans ()
 import UnliftIO.Exception.Lens (handling)
 
-newtype AwsEnv = AwsEnv
-  { unAwsEnv :: Env
-  }
-
-unL :: Lens' AwsEnv Env
-unL = lens unAwsEnv $ \x y -> x {unAwsEnv = y}
-
-awsEnvDiscover :: MonadLoggerIO m => m AwsEnv
-awsEnvDiscover = do
-  env <- liftIO $ newEnv discover
-  AwsEnv <$> configureLogging env
-
-configureLogging :: MonadLoggerIO m => Env -> m Env
-configureLogging env = do
+discover :: MonadLoggerIO m => m Amazonka.Env
+discover = do
+  env <- liftIO $ Amazonka.newEnv Amazonka.discover
   loggerIO <- askLoggerIO
 
   let logger level = do
         loggerIO
-          defaultLoc -- TODO: there may be a way to get a CallStack/Loc
+          defaultLoc
           "Amazonka"
           ( case level of
-              AWS.Info -> LevelInfo
-              AWS.Error -> LevelError
-              AWS.Debug -> LevelDebug
-              AWS.Trace -> LevelOther "trace"
+              Amazonka.Info -> LevelInfo
+              Amazonka.Error -> LevelError
+              Amazonka.Debug -> LevelDebug
+              Amazonka.Trace -> LevelOther "trace"
           )
           . toLogStr
-  pure $ env & env_logger .~ logger
-
-class HasAwsEnv env where
-  awsEnvL :: Lens' env AwsEnv
-
-instance HasAwsEnv AwsEnv where
-  awsEnvL = id
-
-awsWithAuth
-  :: (MonadIO m, MonadReader env m, HasAwsEnv env) => (AuthEnv -> m a) -> m a
-awsWithAuth f = do
-  auth <- view $ awsEnvL . unL . env_auth . to runIdentity
-  withAuth auth f
+  pure $ env & Amazonka.env_logger .~ logger
 
-awsSimple
-  :: forall a env m b
+simple
+  :: forall a m b
    . ( HasCallStack
-     , MonadResource m
-     , MonadReader env m
-     , HasAwsEnv env
+     , MonadIO m
+     , MonadAWS m
      , AWSRequest a
      , Typeable a
      , Typeable (AWSResponse a)
@@ -97,8 +78,8 @@
   => a
   -> (AWSResponse a -> Maybe b)
   -> m b
-awsSimple req post = do
-  resp <- awsSend req
+simple req post = do
+  resp <- send req
 
   let
     name = show $ typeRep $ Proxy @a
@@ -106,53 +87,8 @@
 
   maybe (throwString err) pure $ post resp
 
-awsSend
-  :: ( MonadResource m
-     , MonadReader env m
-     , HasAwsEnv env
-     , AWSRequest a
-     , Typeable a
-     , Typeable (AWSResponse a)
-     )
-  => a
-  -> m (AWSResponse a)
-awsSend req = do
-  AwsEnv env <- view awsEnvL
-  send env req
-
-awsPaginate
-  :: ( MonadResource m
-     , MonadReader env m
-     , HasAwsEnv env
-     , AWSPager a
-     , Typeable a
-     , Typeable (AWSResponse a)
-     )
-  => a
-  -> ConduitM () (AWSResponse a) m ()
-awsPaginate req = do
-  AwsEnv env <- view awsEnvL
-  paginateEither env req >>= hoistEither
-
-hoistEither :: MonadIO m => Either Error a -> m a
-hoistEither = either (liftIO . throwIO) pure
-
-awsAwait
-  :: ( MonadResource m
-     , MonadReader env m
-     , HasAwsEnv env
-     , AWSRequest a
-     , Typeable a
-     )
-  => Wait a
-  -> a
-  -> m Accept
-awsAwait w req = do
-  AwsEnv env <- view awsEnvL
-  await env w req
-
-awsAssumeRole
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env)
+assumeRole
+  :: (MonadIO m, MonadAWS m)
   => Text
   -- ^ Role ARN
   -> Text
@@ -160,31 +96,21 @@
   -> m a
   -- ^ Action to run as the assumed role
   -> m a
-awsAssumeRole role sessionName f = do
+assumeRole role sessionName f = do
   let req = newAssumeRole role sessionName
 
-  assumeEnv <- awsSimple req $ \resp -> do
+  assumeEnv <- simple req $ \resp -> do
     let creds = resp ^. assumeRoleResponse_credentials
-    token <- creds ^. authEnv_sessionToken
+    token <- creds ^. Amazonka.authEnv_sessionToken
 
     let
-      accessKeyId = creds ^. authEnv_accessKeyId
-      secretAccessKey = creds ^. authEnv_secretAccessKey . _Sensitive
-
-    pure $ fromSession accessKeyId secretAccessKey $ token ^. _Sensitive
-
-  local (awsEnvL . unL %~ assumeEnv) f
-
-awsWithin :: (MonadReader env m, HasAwsEnv env) => Region -> m a -> m a
-awsWithin r = local $ awsEnvL . unL . env_region .~ r
+      accessKeyId = creds ^. Amazonka.authEnv_accessKeyId
+      secretAccessKey = creds ^. Amazonka.authEnv_secretAccessKey . _Sensitive
+      sessionToken = token ^. _Sensitive
 
-awsTimeout :: (MonadReader env m, HasAwsEnv env) => Seconds -> m a -> m a
-awsTimeout t = local $ awsEnvL . unL %~ globalTimeout t
+    pure $ fromSession accessKeyId secretAccessKey sessionToken
 
-awsSilently :: (MonadReader env m, HasAwsEnv env) => m a -> m a
-awsSilently = local $ awsEnvL . unL . env_logger .~ noop
- where
-  noop _level _msg = pure ()
+  localEnv assumeEnv f
 
 newtype AccountId = AccountId
   { unAccountId :: Text
diff --git a/src/Stackctl/AWS/EC2.hs b/src/Stackctl/AWS/EC2.hs
--- a/src/Stackctl/AWS/EC2.hs
+++ b/src/Stackctl/AWS/EC2.hs
@@ -6,13 +6,13 @@
 
 import Amazonka.EC2.DescribeAvailabilityZones
 import Amazonka.EC2.Types (AvailabilityZone (..))
-import Stackctl.AWS.Core
+import Stackctl.AWS.Core as AWS
 
 awsEc2DescribeFirstAvailabilityZoneRegionName
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => m Region
+  :: (MonadIO m, MonadAWS m) => m Region
 awsEc2DescribeFirstAvailabilityZoneRegionName = do
   let req = newDescribeAvailabilityZones
-  awsSimple req $ \resp -> do
+  AWS.simple req $ \resp -> do
     azs <- resp ^. describeAvailabilityZonesResponse_availabilityZones
     az <- listToMaybe azs
     rn <- regionName az
diff --git a/src/Stackctl/AWS/Lambda.hs b/src/Stackctl/AWS/Lambda.hs
--- a/src/Stackctl/AWS/Lambda.hs
+++ b/src/Stackctl/AWS/Lambda.hs
@@ -10,10 +10,11 @@
 
 import Stackctl.Prelude hiding (trace)
 
+import Amazonka (globalTimeout)
 import Amazonka.Lambda.Invoke
 import Data.Aeson
 import qualified Data.ByteString.Lazy as BSL
-import Stackctl.AWS.Core
+import Stackctl.AWS.Core as AWS
 
 data LambdaInvokeResult
   = LambdaInvokeSuccess ByteString
@@ -63,10 +64,9 @@
   deriving anyclass (FromJSON, ToJSON)
 
 awsLambdaInvoke
-  :: ( MonadResource m
+  :: ( MonadIO m
      , MonadLogger m
-     , MonadReader env m
-     , HasAwsEnv env
+     , MonadAWS m
      , ToJSON a
      )
   => Text
@@ -78,8 +78,8 @@
 
   -- Match Lambda's own limit (15 minutes) and add some buffer
   resp <-
-    awsTimeout 905
-      $ awsSend
+    AWS.localEnv (globalTimeout 905)
+      $ AWS.send
       $ newInvoke name
       $ BSL.toStrict
       $ encode
diff --git a/src/Stackctl/AWS/STS.hs b/src/Stackctl/AWS/STS.hs
--- a/src/Stackctl/AWS/STS.hs
+++ b/src/Stackctl/AWS/STS.hs
@@ -5,10 +5,9 @@
 import Stackctl.Prelude
 
 import Amazonka.STS.GetCallerIdentity
-import Stackctl.AWS.Core
+import Stackctl.AWS.Core as AWS
 
-awsGetCallerIdentityAccount
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => m AccountId
+awsGetCallerIdentityAccount :: (MonadIO m, MonadAWS m) => m AccountId
 awsGetCallerIdentityAccount = do
-  awsSimple newGetCallerIdentity $ \resp -> do
+  AWS.simple newGetCallerIdentity $ \resp -> do
     AccountId <$> resp ^. getCallerIdentityResponse_account
diff --git a/src/Stackctl/AWS/Scope.hs b/src/Stackctl/AWS/Scope.hs
--- a/src/Stackctl/AWS/Scope.hs
+++ b/src/Stackctl/AWS/Scope.hs
@@ -64,8 +64,7 @@
 instance HasAwsScope AwsScope where
   awsScopeL = id
 
-fetchAwsScope
-  :: (MonadResource m, MonadReader env m, HasAwsEnv env) => m AwsScope
+fetchAwsScope :: (MonadIO m, MonadAWS m) => m AwsScope
 fetchAwsScope =
   AwsScope
     <$> awsGetCallerIdentityAccount
diff --git a/src/Stackctl/Action.hs b/src/Stackctl/Action.hs
--- a/src/Stackctl/Action.hs
+++ b/src/Stackctl/Action.hs
@@ -92,11 +92,11 @@
   deriving anyclass (Exception)
 
 runActions
-  :: ( MonadResource m
+  :: ( MonadIO m
      , MonadLogger m
+     , MonadAWS m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => StackName
   -> ActionOn
@@ -109,11 +109,11 @@
 shouldRunOn Action {on} on' = on == on'
 
 runAction
-  :: ( MonadResource m
+  :: ( MonadIO m
      , MonadLogger m
+     , MonadAWS m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => StackName
   -> Action
diff --git a/src/Stackctl/AutoSSO.hs b/src/Stackctl/AutoSSO.hs
--- a/src/Stackctl/AutoSSO.hs
+++ b/src/Stackctl/AutoSSO.hs
@@ -13,7 +13,7 @@
 import Data.Semigroup (Last (..))
 import qualified Env
 import Options.Applicative
-import Stackctl.AWS.Core (formatServiceError)
+import Stackctl.AWS.Core as AWS (formatServiceError)
 import Stackctl.Prompt
 import System.Process.Typed
 import UnliftIO.Exception.Lens (catching)
diff --git a/src/Stackctl/CLI.hs b/src/Stackctl/CLI.hs
--- a/src/Stackctl/CLI.hs
+++ b/src/Stackctl/CLI.hs
@@ -8,9 +8,11 @@
 import Stackctl.Prelude
 
 import qualified Blammo.Logging.LogSettings.Env as LoggingEnv
+import Control.Monad.AWS as AWS
+import Control.Monad.AWS.ViaReader as AWS
 import Control.Monad.Catch (MonadCatch)
-import Control.Monad.Trans.Resource (ResourceT, runResourceT)
-import Stackctl.AWS
+import Control.Monad.Trans.Resource (MonadResource, ResourceT, runResourceT)
+import qualified Stackctl.AWS.Core as AWS
 import Stackctl.AWS.Scope
 import Stackctl.AutoSSO
 import Stackctl.ColorOption
@@ -24,7 +26,7 @@
   , appConfig :: Config
   , appOptions :: options
   , appAwsScope :: AwsScope
-  , appAwsEnv :: AwsEnv
+  , appAwsEnv :: AWS.Env
   }
 
 optionsL :: Lens' (App options) options
@@ -39,8 +41,8 @@
 instance HasAwsScope (App options) where
   awsScopeL = lens appAwsScope $ \x y -> x {appAwsScope = y}
 
-instance HasAwsEnv (App options) where
-  awsEnvL = lens appAwsEnv $ \x y -> x {appAwsEnv = y}
+instance AWS.HasEnv (App options) where
+  envL = lens appAwsEnv $ \x y -> x {appAwsEnv = y}
 
 instance HasDirectoryOption options => HasDirectoryOption (App options) where
   directoryOptionL = optionsL . directoryOptionL
@@ -73,6 +75,7 @@
     , MonadCatch
     , MonadMask
     )
+  deriving (MonadAWS) via (ReaderAWS (AppT app m))
 
 runAppT
   :: ( MonadMask m
@@ -99,12 +102,12 @@
         envLogSettings
 
   app <- runResourceT $ runLoggerLoggingT logger $ do
-    aws <- runReaderT (handleAutoSSO options awsEnvDiscover) logger
+    aws <- runReaderT (handleAutoSSO options AWS.discover) logger
 
     App logger
       <$> loadConfigOrExit
       <*> pure options
-      <*> runReaderT fetchAwsScope aws
+      <*> AWS.runEnvT fetchAwsScope aws
       <*> pure aws
 
   let
diff --git a/src/Stackctl/RemovedStack.hs b/src/Stackctl/RemovedStack.hs
--- a/src/Stackctl/RemovedStack.hs
+++ b/src/Stackctl/RemovedStack.hs
@@ -7,16 +7,15 @@
 import Control.Error.Util (hoistMaybe)
 import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
 import Stackctl.AWS.CloudFormation
-import Stackctl.AWS.Core
+import Stackctl.AWS.Core as AWS
 import Stackctl.AWS.Scope
 import Stackctl.FilterOption
 import UnliftIO.Directory (doesFileExist)
 
 inferRemovedStacks
   :: ( MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadReader env m
-     , HasAwsEnv env
      , HasAwsScope env
      , HasFilterOption env
      )
@@ -27,7 +26,7 @@
   catMaybes <$> traverse (findRemovedStack scope) paths
 
 findRemovedStack
-  :: (MonadUnliftIO m, MonadResource m, MonadReader env m, HasAwsEnv env)
+  :: (MonadUnliftIO m, MonadAWS m)
   => AwsScope
   -> FilePath
   -> m (Maybe Stack)
diff --git a/src/Stackctl/Spec/Capture.hs b/src/Stackctl/Spec/Capture.hs
--- a/src/Stackctl/Spec/Capture.hs
+++ b/src/Stackctl/Spec/Capture.hs
@@ -77,11 +77,10 @@
 runCapture
   :: ( MonadMask m
      , MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasAwsScope env
-     , HasAwsEnv env
      , HasConfig env
      , HasDirectoryOption env
      )
diff --git a/src/Stackctl/Spec/Cat.hs b/src/Stackctl/Spec/Cat.hs
--- a/src/Stackctl/Spec/Cat.hs
+++ b/src/Stackctl/Spec/Cat.hs
@@ -54,8 +54,8 @@
       )
 
 runCat
-  :: ( MonadMask m
-     , MonadResource m
+  :: ( MonadIO m
+     , MonadMask m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
diff --git a/src/Stackctl/Spec/Changes.hs b/src/Stackctl/Spec/Changes.hs
--- a/src/Stackctl/Spec/Changes.hs
+++ b/src/Stackctl/Spec/Changes.hs
@@ -52,12 +52,11 @@
 runChanges
   :: ( MonadMask m
      , MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
      , HasAwsScope env
-     , HasAwsEnv env
      , HasConfig env
      , HasDirectoryOption env
      , HasFilterOption env
diff --git a/src/Stackctl/Spec/Deploy.hs b/src/Stackctl/Spec/Deploy.hs
--- a/src/Stackctl/Spec/Deploy.hs
+++ b/src/Stackctl/Spec/Deploy.hs
@@ -71,12 +71,11 @@
 runDeploy
   :: ( MonadMask m
      , MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
      , HasAwsScope env
-     , HasAwsEnv env
      , HasConfig env
      , HasDirectoryOption env
      , HasFilterOption env
@@ -114,12 +113,12 @@
           when sdoClean $ awsCloudFormationDeleteAllChangeSets stackName
 
 deleteRemovedStack
-  :: ( MonadMask m
-     , MonadResource m
+  :: ( MonadIO m
+     , MonadMask m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => DeployConfirmation
   -> Stack
@@ -146,11 +145,10 @@
 
 checkIfStackRequiresDeletion
   :: ( MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => DeployConfirmation
   -> StackName
@@ -174,7 +172,7 @@
     deleteStack stackName
 
 deleteStack
-  :: (MonadResource m, MonadLogger m, MonadReader env m, HasAwsEnv env)
+  :: (MonadIO m, MonadAWS m, MonadLogger m)
   => StackName
   -> m ()
 deleteStack stackName = do
@@ -186,11 +184,10 @@
 
 deployChangeSet
   :: ( MonadUnliftIO m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => DeployConfirmation
   -> ChangeSet
@@ -232,11 +229,11 @@
   changeSetId = csChangeSetId changeSet
 
 tailStackEventsSince
-  :: ( MonadResource m
+  :: ( MonadIO m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasLogger env
-     , HasAwsEnv env
      )
   => StackName
   -> Maybe Text
diff --git a/src/Stackctl/Spec/Discover.hs b/src/Stackctl/Spec/Discover.hs
--- a/src/Stackctl/Spec/Discover.hs
+++ b/src/Stackctl/Spec/Discover.hs
@@ -21,8 +21,8 @@
 import System.FilePath.Glob
 
 forEachSpec_
-  :: ( MonadMask m
-     , MonadResource m
+  :: ( MonadIO m
+     , MonadMask m
      , MonadLogger m
      , MonadReader env m
      , HasAwsScope env
@@ -35,8 +35,8 @@
 forEachSpec_ f = traverse_ f =<< discoverSpecs
 
 discoverSpecs
-  :: ( MonadMask m
-     , MonadResource m
+  :: ( MonadIO m
+     , MonadMask m
      , MonadLogger m
      , MonadReader env m
      , HasAwsScope env
diff --git a/src/Stackctl/Spec/List.hs b/src/Stackctl/Spec/List.hs
--- a/src/Stackctl/Spec/List.hs
+++ b/src/Stackctl/Spec/List.hs
@@ -37,11 +37,10 @@
 runList
   :: ( MonadUnliftIO m
      , MonadMask m
-     , MonadResource m
+     , MonadAWS m
      , MonadLogger m
      , MonadReader env m
      , HasAwsScope env
-     , HasAwsEnv env
      , HasLogger env
      , HasConfig env
      , HasDirectoryOption env
diff --git a/src/Stackctl/StackSpec.hs b/src/Stackctl/StackSpec.hs
--- a/src/Stackctl/StackSpec.hs
+++ b/src/Stackctl/StackSpec.hs
@@ -185,10 +185,8 @@
 -- | Create a Change Set between a Stack Specification and deployed state
 createChangeSet
   :: ( MonadUnliftIO m
-     , MonadResource m
      , MonadLogger m
-     , MonadReader env m
-     , HasAwsEnv env
+     , MonadAWS m
      )
   => StackSpec
   -> [Parameter]
diff --git a/stackctl.cabal b/stackctl.cabal
--- a/stackctl.cabal
+++ b/stackctl.cabal
@@ -1,6 +1,6 @@
 cabal-version:   1.18
 name:            stackctl
-version:         1.5.0.1
+version:         1.6.0.0
 license:         MIT
 license-file:    LICENSE
 copyright:       2022 Renaissance Learning Inc
@@ -84,45 +84,46 @@
 
     build-depends:
         Blammo >=1.1.2.1,
-        Glob,
-        QuickCheck,
-        aeson,
-        aeson-casing,
-        aeson-pretty,
+        Glob >=0.10.2,
+        QuickCheck >=2.14.2,
+        aeson >=2.0.3.0,
+        aeson-casing >=0.2.0.0,
+        aeson-pretty >=0.8.9,
         amazonka >=2.0,
         amazonka-cloudformation >=2.0,
         amazonka-core >=2.0,
         amazonka-ec2 >=2.0,
         amazonka-lambda >=2.0,
+        amazonka-mtl >=0.1.1.0,
         amazonka-sso >=2.0,
         amazonka-sts >=2.0,
         base >=4 && <5,
-        bytestring,
+        bytestring >=0.11.3.1,
         cfn-flip >=0.1.0.3,
-        conduit,
-        containers,
-        envparse,
-        errors,
-        exceptions,
-        extra,
-        filepath,
-        lens,
-        lens-aeson,
-        monad-logger,
-        mtl,
-        optparse-applicative,
-        resourcet,
-        rio,
-        semigroups,
-        text,
-        text-metrics,
-        time,
-        transformers,
-        typed-process,
+        conduit >=1.3.4.3,
+        containers >=0.6.5.1,
+        envparse >=0.5.0,
+        errors >=2.3.0,
+        exceptions >=0.10.4,
+        extra >=1.7.12,
+        filepath >=1.4.2.2,
+        lens >=5.1.1,
+        lens-aeson >=1.2.2,
+        monad-logger >=0.3.37,
+        mtl >=2.2.2,
+        optparse-applicative >=0.17.0.0,
+        resourcet >=1.2.6,
+        rio >=0.1.22.0,
+        semigroups >=0.20,
+        text >=1.2.5.0,
+        text-metrics >=0.3.2,
+        time >=1.11.1.1,
+        transformers >=0.5.6.2,
+        typed-process >=0.2.10.1,
         unliftio >=0.2.25.0,
-        unordered-containers,
-        uuid,
-        yaml
+        unordered-containers >=0.2.19.1,
+        uuid >=1.3.15,
+        yaml >=0.11.8.0
 
 executable stackctl
     main-is:            Main.hs
@@ -187,14 +188,14 @@
         -optP-Wno-nonportable-include-path
 
     build-depends:
-        Glob,
-        QuickCheck,
-        aeson,
+        Glob >=0.10.2,
+        QuickCheck >=2.14.2,
+        aeson >=2.0.3.0,
         base >=4 && <5,
-        bytestring,
-        filepath,
-        hspec,
+        bytestring >=0.11.3.1,
+        filepath >=1.4.2.2,
+        hspec >=2.9.7,
         hspec-golden >=0.2.1.0,
-        mtl,
+        mtl >=2.2.2,
         stackctl,
-        yaml
+        yaml >=0.11.8.0
