diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+# About
+
+Testcontainers is a Haskell library that provides a friendly API to run Docker
+containers. It is designed to create a runtime environment to use during your
+integration tests
+
+# Example
+
+``` haskell
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+module Main where
+
+import qualified Test.Tasty           as Tasty
+import qualified Test.Tasty.HUnit     as Tasty
+import qualified TestContainers.Tasty as TC
+
+
+data Endpoints = Endpoints
+  {
+    redisHost :: String
+  , redisPort :: Int
+  }
+
+
+-- | Sets up and runs the containers required for this test suite.
+setupContainers :: TC.MonadDocker m => m Endpoints
+setupContainers = do
+
+  -- Launch the container based on the redis:5.0.0 image.
+  redisContainer <- TC.run $ TC.containerRequest (TC.fromTag "redis:5.0.0")
+    -- Expose the port 6379 from within the container. The respective port
+    -- on the host machine can be looked up using `containerPort` (see below).
+    TC.& TC.setExpose [ 6379 ]
+    -- Wait until the container is ready to accept requests. `run` blocks until
+    -- readiness can be established.
+    TC.& TC.setWaitingFor (TC.waitUntilMappedPortReachable 6379)
+
+  pure $ Endpoints
+    {
+      redisHost = "0.0.0.0"
+    , redisPort =
+        -- Look up the corresponding port on the host machine for the exposed
+        -- port 6379.
+        TC.containerPort redisContainer 6379
+    }
+
+
+main :: IO ()
+main =
+  Tasty.defaultMain $
+  -- Use `withContainers` to make the containers available in the closed over
+  -- tests. Due to how Tasty handles resources `withContainers` passes down
+  -- an IO action `start` to actually start up the containers. `start` can be
+  -- invoked multiple times, Tasty makes sure to only start up the containrs
+  -- once.
+  --
+  -- `withContainers` ensures the started containers are shut down correctly
+  -- once execution leaves its scope.
+  TC.withContainers setupContainers $ \start ->
+    Tasty.testGroup "Some test group"
+      [
+        Tasty.testCase "Redis test" $ do
+          -- Actually start the containers!!
+          Endpoints {..} <- start
+          -- ... assert some properties
+          pure ()
+
+      , Tasty.testCase "Another Redis test" $ do
+          -- Invoking `start` twice gives the same Endpoints!
+          Endpoints {..} <- start
+          -- ... assert some properties
+          pure ()
+      ]
+```
diff --git a/src/TestContainers.hs b/src/TestContainers.hs
--- a/src/TestContainers.hs
+++ b/src/TestContainers.hs
@@ -80,9 +80,14 @@
 
   , Config(..)
   , defaultDockerConfig
-  , dockerForMacConfig
   , determineConfig
 
+    -- *
+
+  , Tracer
+  , Trace(..)
+  , newTracer
+
     -- * Exceptions
 
   , M.DockerException(..)
@@ -91,8 +96,8 @@
 
     -- * Misc. Docker functions
 
-  , dockerVersion
-  , isDockerForDesktop
+  , dockerHostOs
+  , isDockerOnLinux
 
     -- * Predefined Docker images
 
@@ -108,5 +113,3 @@
 
 import           TestContainers.Docker as M
 import           TestContainers.Image  as M
-
-import           Data.Function         as M ((&))
diff --git a/src/TestContainers/Docker.hs b/src/TestContainers/Docker.hs
--- a/src/TestContainers/Docker.hs
+++ b/src/TestContainers/Docker.hs
@@ -1,12 +1,17 @@
-{-# LANGUAGE BangPatterns          #-}
-{-# LANGUAGE ConstraintKinds       #-}
-{-# LANGUAGE DuplicateRecordFields #-}
-{-# LANGUAGE FlexibleContexts      #-}
-{-# LANGUAGE NamedFieldPuns        #-}
-{-# LANGUAGE OverloadedStrings     #-}
-{-# LANGUAGE PatternSynonyms       #-}
-{-# LANGUAGE RankNTypes            #-}
-{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE BangPatterns               #-}
+{-# LANGUAGE ConstraintKinds            #-}
+{-# LANGUAGE DerivingStrategies         #-}
+{-# LANGUAGE DuplicateRecordFields      #-}
+{-# LANGUAGE FlexibleContexts           #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE NamedFieldPuns             #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE PatternSynonyms            #-}
+{-# LANGUAGE RankNTypes                 #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+{-# LANGUAGE UndecidableInstances       #-}
 module TestContainers.Docker
   (
     MonadDocker
@@ -15,9 +20,15 @@
 
   , Config(..)
   , defaultDockerConfig
-  , dockerForMacConfig
   , determineConfig
 
+  -- * Exeuction tracing
+
+  , Tracer
+  , Trace(..)
+  , newTracer
+  , withTrace
+
   -- * Docker image
 
   , ImageTag
@@ -89,8 +100,8 @@
 
   -- * Misc. Docker functions
 
-  , dockerVersion
-  , isDockerForDesktop
+  , dockerHostOs
+  , isDockerOnLinux
 
   -- * Wait until a socket is reachable
   , waitUntilMappedPortReachable
@@ -102,11 +113,9 @@
   , (&)
   ) where
 
+import           Control.Applicative          ((<|>))
 import           Control.Concurrent           (threadDelay)
 import           Control.Exception            (IOException, throw)
-import           Optics.Operators             ((^?))
-import           Optics.Optic                 ((%))
-import           Optics.Fold                  (pre)
 import           Control.Monad.Catch          (Exception, MonadCatch, MonadMask,
                                                MonadThrow, bracket, throwM, try)
 import           Control.Monad.Fix            (mfix)
@@ -119,16 +128,18 @@
 import           Data.Aeson                   (Value, decode')
 import qualified Data.Aeson.Optics            as Optics
 import qualified Data.ByteString.Lazy.Char8   as LazyByteString
+import           Data.Foldable                (traverse_)
 import           Data.Function                ((&))
 import           Data.List                    (find)
-import           Data.Text                    (Text, isInfixOf, pack, strip,
-                                               unpack)
+import           Data.Text                    (Text, pack, strip, unpack)
 import           Data.Text.Encoding.Error     (lenientDecode)
 import qualified Data.Text.Lazy               as LazyText
 import qualified Data.Text.Lazy.Encoding      as LazyText
-import           GHC.Stack                    (HasCallStack,
-                                               withFrozenCallStack)
+import           GHC.Stack                    (withFrozenCallStack)
 import qualified Network.Socket               as Socket
+import           Optics.Fold                  (pre)
+import           Optics.Operators             ((^?))
+import           Optics.Optic                 ((%))
 import           Prelude                      hiding (error, id)
 import qualified Prelude
 import           System.Exit                  (ExitCode (..))
@@ -138,24 +149,66 @@
 import           System.Timeout               (timeout)
 
 
+-- | Type representing various events during testcontainer execution.
+data Trace
+  -- | The low-level invocation of @docker@ command
+  --
+  -- @
+  --   TraceDockerInvocation args stdin exitcode
+  -- @
+  = TraceDockerInvocation [Text] Text ExitCode -- docker [args] [stdin]
+  -- | Line written to STDOUT by a Docker process.
+  | TraceDockerStdout Text
+  -- | Line written to STDERR by a Docker process.
+  | TraceDockerStderr Text
+  -- | Waiting for a container to become ready. Attached with the
+  -- timeout to wait (in seconds).
+  | TraceWaitUntilReady (Maybe Int)
+  -- | Opening socket
+  | TraceOpenSocket Text Int (Maybe IOException)
+
+
+deriving stock instance Eq Trace
+deriving stock instance Show Trace
+
+
+-- | Traces execution within testcontainers library.
+newtype Tracer = Tracer { unTracer :: Trace -> IO () }
+
+
+deriving newtype instance Semigroup Tracer
+deriving newtype instance Monoid Tracer
+
+
+-- | Construct a new `Tracer` from a tracing function.
+newTracer
+  :: (Trace -> IO ())
+  -> Tracer
+newTracer action = Tracer
+  {
+    unTracer = action
+  }
+
+
+withTrace :: MonadIO m => Tracer -> Trace -> m ()
+withTrace tracer trace =
+  liftIO $ unTracer tracer trace
+{-# INLINE withTrace #-}
+
+
 -- | Configuration for defaulting behavior.
 --
--- Note that IP address returned by `containerIp` is not reachable when using
--- Docker For Mac (See https://docs.docker.com/docker-for-mac/networking/#per-container-ip-addressing-is-not-possible).
---
--- If you are targeting Docker For Mac you should use `dockerForMacConfig`, instead
--- use `defaultDockerConfig`.
---
 -- @since 0.2.0.0
 --
 data Config = Config
   {
-    -- | How to retrieve the IP address of a Docker container.
-    -- There some known limitations around Docker for Mac in that
-    -- it doesn't support accessing containers by their IP (see `containerIp`).
+    -- | The number of seconds to maximally wait for a container to
+    -- become ready. Default is `Just 60`.
     --
-    -- This configuration let's you define how to access the IP.
-    configContainerIp :: Container -> Text
+    -- @Nothing@ <=> waits indefinitely.
+    configDefaultWaitTimeout :: Maybe Int
+    -- | Traces execution inside testcontainers library.
+  , configTracer             :: Tracer
   }
 
 
@@ -166,32 +219,18 @@
 defaultDockerConfig :: Config
 defaultDockerConfig = Config
   {
-    configContainerIp = internalContainerIp
-  }
-
-
--- | A default configuration to use with Docker for Mac installations. It doesn't
--- use a Docker container's IP address to access a container, instead it always uses
--- the loopback interface @0.0.0.0@.
---
--- @since 0.2.0.0
---
-dockerForMacConfig :: Config
-dockerForMacConfig = defaultDockerConfig
-  {
-    configContainerIp = \_ -> "0.0.0.0"
+    configDefaultWaitTimeout = Just 60
+  , configTracer = mempty
   }
 
 
 -- | Autoselect the default configuration depending on wether you use Docker For
--- Mac/Desktop or not.
+-- Mac or not.
 determineConfig :: IO Config
-determineConfig = do
-  -- TODO We used to be clever here but it turned out that it wouldn't let
-  -- hosts reach containers. Even on linux! Figure out how to do Docker
-  -- networking properly.
-  pure dockerForMacConfig
+determineConfig =
+  pure defaultDockerConfig
 
+
 -- | Failing to interact with Docker results in this exception
 -- being thrown.
 --
@@ -388,8 +427,10 @@
       [ [ tag ] ] ++
       [ command | Just command <- [cmd] ]
 
-  stdout <- docker dockerRun
+  config@Config { configTracer } <- ask
 
+  stdout <- docker configTracer dockerRun
+
   let
     id :: ContainerId
     !id =
@@ -398,9 +439,8 @@
 
     -- Careful, this is really meant to be lazy
     ~inspectOutput = unsafePerformIO $
-      internalInspect id
+      internalInspect configTracer id
 
-  config <- ask
   container <- liftResourceT $ mfix $ \container -> do
       -- Note: We have to tie the knot as the resulting container
       -- carries the release key as well.
@@ -428,9 +468,9 @@
 --
 -- @since 0.1.0.0
 --
-docker :: MonadIO m => [Text] -> m String
-docker args =
-  dockerWithStdin args ""
+docker :: MonadIO m => Tracer -> [Text] -> m String
+docker tracer args =
+  dockerWithStdin tracer args ""
 
 
 -- | Internal function that runs Docker. Takes care of throwing an exception
@@ -438,11 +478,17 @@
 --
 -- @since 0.1.0.0
 --
-dockerWithStdin :: MonadIO m => [Text] -> Text -> m String
-dockerWithStdin args stdin = liftIO $ do
+dockerWithStdin :: MonadIO m => Tracer -> [Text] -> Text -> m String
+dockerWithStdin tracer args stdin = liftIO $ do
   (exitCode, stdout, stderr) <- Process.readProcessWithExitCode "docker"
     (map unpack args) (unpack stdin)
 
+  withTrace tracer (TraceDockerInvocation args stdin exitCode)
+
+  -- TODO output these concurrently with the process
+  traverse_ (withTrace tracer . TraceDockerStdout . pack) (lines stdout)
+  traverse_ (withTrace tracer . TraceDockerStderr . pack) (lines stderr)
+
   case exitCode of
     ExitSuccess -> pure stdout
     _ -> throwM $ DockerException
@@ -458,7 +504,8 @@
 --
 kill :: MonadDocker m => Container -> m ()
 kill Container { id } = do
-  _ <- docker [ "kill", id ]
+  tracer <- askTracer
+  _ <- docker tracer [ "kill", id ]
   return ()
 
 
@@ -468,7 +515,8 @@
 --
 stop :: MonadDocker m => Container -> m ()
 stop Container { id } = do
-  _ <- docker [ "stop", id ]
+  tracer <- askTracer
+  _ <- docker tracer [ "stop", id ]
   return ()
 
 
@@ -478,7 +526,8 @@
 --
 rm :: MonadDocker m => Container -> m ()
 rm Container { id } = do
-  _ <- docker [ "rm", "-f", "-v", id ]
+  tracer <- askTracer
+  _ <- docker tracer [ "rm", "-f", "-v", id ]
   return ()
 
 
@@ -563,7 +612,8 @@
 --
 fromTag :: ImageTag -> ToImage
 fromTag tag = defaultToImage $ do
-  output <- docker [ "pull", "--quiet", tag ]
+  tracer <- askTracer
+  output <- docker tracer [ "pull", "--quiet", tag ]
   return $ Image
     {
       tag = strip (pack output)
@@ -586,7 +636,8 @@
           [ "build", "-f", pack dockerfile, pack path ]
       | otherwise =
           [ "build", pack path ]
-  output <- docker args
+  tracer <- askTracer
+  output <- docker tracer args
   return $ Image
     {
       tag = strip (pack output)
@@ -601,7 +652,8 @@
   :: Text
   -> ToImage
 fromDockerfile dockerfile = defaultToImage $ do
-  output <- dockerWithStdin [ "build", "--quiet", "-" ] dockerfile
+  tracer <- askTracer
+  output <- dockerWithStdin tracer [ "build", "--quiet", "-" ] dockerfile
   return $ Image
     {
       tag = strip (pack output)
@@ -615,33 +667,6 @@
 type ContainerId = Text
 
 
--- | Dumb logger abstraction to allow us to trace container execution.
---
--- @since 0.1.0.0
---
-data Logger = Logger
-  {
-    debug :: forall m . (HasCallStack, MonadIO m) => Text -> m ()
-  , info  :: forall m . (HasCallStack, MonadIO m) => Text -> m ()
-  , warn  :: forall m . (HasCallStack, MonadIO m) => Text -> m ()
-  , error :: forall m . (HasCallStack, MonadIO m) => Text -> m ()
-  }
-
-
--- | Logger that doesn't log anything.
---
--- @since 0.1.0.0
---
-silentLogger :: Logger
-silentLogger = Logger
-  {
-    debug = \_ -> pure ()
-  , info  = \_ -> pure ()
-  , warn  = \_ -> pure ()
-  , error = \_ -> pure ()
-  }
-
-
 -- | A strategy that describes how to asses readiness of a `Container`. Allows
 -- Users to plug in their definition of readiness.
 --
@@ -649,7 +674,7 @@
 --
 newtype WaitUntilReady = WaitUntilReady
   {
-    checkContainerReady :: Logger -> Config -> Container -> ResIO ()
+    checkContainerReady :: Config -> Container -> (Maybe Int, ResIO ())
   }
 
 
@@ -691,15 +716,15 @@
 -- @since 0.1.0.0
 --
 waitUntilTimeout :: Int -> WaitUntilReady -> WaitUntilReady
-waitUntilTimeout seconds wait = WaitUntilReady $ \logger config container@Container{ id } -> do
-  withRunInIO $ \runInIO -> do
-    result <- timeout (seconds * 1000000) $
-      runInIO (checkContainerReady wait logger config container)
-    case result of
-      Nothing ->
-        throwM $ TimeoutException { id }
-      Just _ ->
-        pure ()
+waitUntilTimeout seconds wait = WaitUntilReady $ \config container ->
+  case checkContainerReady wait config container of
+    (Nothing, check) ->
+      (Just seconds, check)
+    (Just innerTimeout, check)
+      | innerTimeout > seconds ->
+          (Just seconds, check)
+      | otherwise ->
+          (Just innerTimeout, check)
 
 
 -- | Waits until the port of a container is ready to accept connections.
@@ -710,19 +735,23 @@
 waitUntilMappedPortReachable
   :: Int
   -> WaitUntilReady
-waitUntilMappedPortReachable port = WaitUntilReady $ \logger _config container ->
-  withFrozenCallStack $ do
+waitUntilMappedPortReachable port = WaitUntilReady $ \config container ->
+  withFrozenCallStack $
 
   let
+    Config { configTracer } = config
+
+    -- TODO add a parameterizable function when we will support host
+    -- mapping exposure
     hostIp :: String
-    hostIp = unpack (containerIp container)
+    hostIp = "0.0.0.0"
 
-    hostPort :: String
-    hostPort = show $ containerPort container port
+    hostPort :: Int
+    hostPort = containerPort container port
 
     resolve = do
       let hints = Socket.defaultHints { Socket.addrSocketType = Socket.Stream }
-      head <$> Socket.getAddrInfo (Just hints) (Just hostIp) (Just hostPort)
+      head <$> Socket.getAddrInfo (Just hints) (Just hostIp) (Just (show hostPort))
 
     open addr = do
       socket <- Socket.socket
@@ -735,21 +764,21 @@
       pure socket
 
     retry = do
-      debug logger $ "Trying to open socket to " <> pack hostIp <> ":" <> pack hostPort
       result <- try (resolve >>= open)
       case result of
         Right socket -> do
-          debug logger $ "Successfully opened socket to " <> pack hostIp <> ":" <> pack hostPort
+          withTrace configTracer (TraceOpenSocket (pack hostIp) hostPort Nothing)
           Socket.close socket
           pure ()
 
         Left (exception :: IOException) -> do
-          debug logger $ "Failed to open socket to " <> pack hostIp <> ":" <>
-            pack hostPort <> " with " <> pack (show exception)
+          withTrace configTracer
+            (TraceOpenSocket (pack hostIp) hostPort (Just exception))
           threadDelay 500000
           retry
 
-  liftIO retry
+  in
+    (Nothing, liftIO retry)
 
 
 -- | A low-level primitive that allows scanning the logs for specific log lines
@@ -761,9 +790,12 @@
 -- @since 0.1.0.0
 --
 waitWithLogs :: (Container -> Handle -> Handle -> IO ()) -> WaitUntilReady
-waitWithLogs waiter = WaitUntilReady $ \_logger config container -> do
-  flip runReaderT config $ withLogs container $ \stdout stderr ->
-    liftIO $ waiter container stdout stderr
+waitWithLogs waiter = WaitUntilReady $ \config container ->
+  let
+    check = flip runReaderT config $ withLogs container $ \stdout stderr ->
+      liftIO $ waiter container stdout stderr
+  in
+    (Nothing, check)
 
 
 -- | A data type indicating which pipe to scan for a specific log line.
@@ -818,11 +850,31 @@
 -- @since 0.1.0.0
 --
 waitUntilReady :: MonadDocker m => Container -> WaitUntilReady -> m ()
-waitUntilReady container waiter = do
-  config <- ask
-  liftResourceT $ checkContainerReady waiter silentLogger config container
+waitUntilReady container@Container { id } waiter = do
+  config@Config { configDefaultWaitTimeout, configTracer } <- ask
+  let
+    (timeoutInSeconds, check) =
+      checkContainerReady waiter config container
 
+    runAction action timeoutSeconds = do
+      withTrace configTracer (TraceWaitUntilReady timeoutSeconds)
+      action
 
+    withTimeout action = case timeoutInSeconds <|> configDefaultWaitTimeout of
+      Nothing ->
+        runAction action Nothing
+      Just seconds ->
+        withRunInIO $ \runInIO -> do
+          result <- timeout (seconds * 1000000) $ runInIO (runAction action (Just seconds))
+          case result of
+            Nothing ->
+              throwM $ TimeoutException { id }
+            Just _ ->
+              pure ()
+
+  liftResourceT (withTimeout check)
+
+
 -- | Handle to a Docker image.
 --
 -- @since 0.1.0.0
@@ -902,8 +954,8 @@
 -- @since 0.1.0.0
 --
 containerIp :: Container -> Text
-containerIp container@Container { config = Config { configContainerIp } } =
-  configContainerIp container
+containerIp =
+  internalContainerIp
 
 
 -- | Get the IP address of a running Docker container using @docker inspect@.
@@ -966,9 +1018,9 @@
 --
 -- @since 0.1.0.0
 --
-internalInspect :: (MonadThrow m, MonadIO m) => ContainerId -> m InspectOutput
-internalInspect id = do
-  stdout <- docker [ "inspect", id ]
+internalInspect :: (MonadThrow m, MonadIO m) => Tracer -> ContainerId -> m InspectOutput
+internalInspect tracer id = do
+  stdout <- docker tracer [ "inspect", id ]
   case decode' (LazyByteString.pack stdout) of
     Nothing ->
       throwM $ InspectOutputInvalidJSON { id }
@@ -980,13 +1032,19 @@
       Prelude.error "Internal: Multiple results where I expected single result"
 
 
-dockerVersion :: (MonadResource m, MonadMask m, MonadIO m) => m Text
-dockerVersion = do
-  stdout <- docker [ "version", "--format", "{{.Server.KernelVersion}}" ]
-  return (pack stdout)
+askTracer :: MonadReader Config m => m Tracer
+askTracer = do
+  Config { configTracer } <- ask
+  pure configTracer
+{-# INLINE askTracer #-}
 
 
-isDockerForDesktop :: (MonadResource m, MonadMask m, MonadIO m) => m Bool
-isDockerForDesktop = do
-  version <- dockerVersion
-  pure $ "linuxkit" `isInfixOf` version
+dockerHostOs :: MonadDocker m => m Text
+dockerHostOs = do
+  tracer <- askTracer
+  strip . pack <$> docker tracer [ "version", "--format", "{{.Server.Os}}" ]
+
+
+isDockerOnLinux :: MonadDocker m => m Bool
+isDockerOnLinux =
+  ("linux" ==) <$> dockerHostOs
diff --git a/src/TestContainers/Tasty.hs b/src/TestContainers/Tasty.hs
--- a/src/TestContainers/Tasty.hs
+++ b/src/TestContainers/Tasty.hs
@@ -3,66 +3,123 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 module TestContainers.Tasty
   (
+    -- * Tasty Ingredient
+    ingredient
+
     -- * Running containers for tests
-    withContainers
+  , withContainers
 
     -- * Re-exports for convenience
   , module Reexports
   ) where
 
+import           Control.Applicative                   ((<|>))
 import           Control.Monad.IO.Class                (liftIO)
 import           Control.Monad.Reader                  (runReaderT)
 import           Control.Monad.Trans.Resource          (InternalState,
-                                                        getInternalState,
-                                                        runResourceT)
+                                                        getInternalState)
 import           Control.Monad.Trans.Resource.Internal (stateAlloc,
                                                         stateCleanup)
 import           Data.Acquire                          (ReleaseType (ReleaseNormal))
-import           Test.Tasty                            (TestTree, withResource)
+import           Data.Data                             (Proxy (Proxy))
+import           Test.Tasty                            (TestTree, askOption,
+                                                        withResource)
+import qualified Test.Tasty                            as Tasty
+import           Test.Tasty.Ingredients                (Ingredient)
+import           Test.Tasty.Options                    (IsOption (..),
+                                                        OptionDescription (..),
+                                                        mkFlagCLParser,
+                                                        safeRead)
+import           TestContainers                        as Reexports hiding
+                                                                    (Trace)
 
-import           TestContainers                        (Container,
-                                                        ContainerRequest,
-                                                        MonadDocker, ToImage,
-                                                        stop)
-import           TestContainers                        as Reexports
 
+newtype DefaultTimeout = DefaultTimeout (Maybe Int)
 
--- | Allow `Tasty.TestTree` to depend on Docker containers. Tasty takes care of
--- initialization and de-initialization of the containers.
+
+instance IsOption DefaultTimeout where
+
+  defaultValue =
+    DefaultTimeout Nothing
+
+  parseValue =
+    fmap (DefaultTimeout . Just) . safeRead
+
+  optionName =
+    pure "testcontainers-default-timeout"
+
+  optionHelp =
+    pure "The max. number of seconds to wait for a container to become ready"
+
+
+newtype Trace = Trace Bool
+
+
+instance IsOption Trace where
+
+  defaultValue =
+    Trace False
+
+  parseValue =
+    const Nothing
+
+  optionCLParser =
+    mkFlagCLParser mempty (Trace True)
+
+  optionName =
+    pure "testcontainers-trace"
+
+  optionHelp =
+    pure "Turns on tracing of the underlying Docker operations"
+
+
+-- | Tasty `Ingredient` that adds useful options to control defaults within the
+-- TetContainers library.
 --
 -- @
---
--- containers :: MonadDocker m => m ()
--- containers = do
---   _redis <- TestContainers.run $ TestContainers.containerRequest TestContainers.redis
---   _kafka <- TestContainers.run $ TestContainers.containerRequest TestContainers.kafka
---   pure ()
---
--- example :: TestTree
--- example =
---   withContainers containers $ \\runContainers -> testGroup "Example tests"
---     [
---       testCase "first test" $ do
---         -- Actually runs the container.
---         runContainers
---       testCase "second test" $ do
---         -- Start containers. Tasty makes sure to only initialize once as
---         --  `first test` might already have started them.
---         runContainers
---     ]
+-- main :: IO ()
+-- main = `Tasty.defaultMainWithIngredients` (`ingredient` : `Tasty.defaultIngredients`) tests
 -- @
 --
--- `withContainers` allows you naturally scope the handling of containers for your tests.
+-- @since 0.3.0.0
+--
+ingredient :: Ingredient
+ingredient = Tasty.includingOptions
+  [
+    Option (Proxy :: Proxy DefaultTimeout)
+  , Option (Proxy :: Proxy Trace)
+  ]
+
+
 withContainers
   :: forall a
   .  (forall m. MonadDocker m => m a)
   -> (IO a -> TestTree)
   -> TestTree
 withContainers startContainers tests =
+  askOption $ \ (DefaultTimeout defaultTimeout) ->
+  askOption $ \ (Trace enableTrace) ->
   let
+    tracer :: Tracer
+    tracer
+      | enableTrace = newTracer $ \message ->
+          putStrLn (show message)
+      | otherwise =
+          mempty
+
     runC action = do
       config <- determineConfig
-      runReaderT (runResourceT action) config
+
+      let
+        actualConfig :: Config
+        actualConfig = config
+          {
+            configDefaultWaitTimeout =
+              defaultTimeout <|> configDefaultWaitTimeout config
+          , configTracer = tracer
+          }
+
+      runReaderT (runResourceT action) actualConfig
 
     -- Correct resource handling is tricky here:
     -- Tasty offers a bracket alike in IO. We  have
diff --git a/test/Driver.hs b/test/Driver.hs
--- a/test/Driver.hs
+++ b/test/Driver.hs
@@ -1,1 +1,1 @@
-{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display #-}
+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display -optF --ingredient -optF TestContainers.Tasty.ingredient #-}
diff --git a/testcontainers.cabal b/testcontainers.cabal
--- a/testcontainers.cabal
+++ b/testcontainers.cabal
@@ -1,6 +1,6 @@
 cabal-version:       >=1.10
 name:                testcontainers
-version:             0.2.0.0
+version:             0.3.0.0
 synopsis:            Docker containers for your integration tests.
 description:         testcontainers is a Haskell library that provides a friendly API to
                      run Docker containers. It is designed to create a runtime environment
@@ -13,7 +13,8 @@
 copyright:           2020 Alex Biehl
 category:            Development
 build-type:          Simple
-extra-source-files:  CHANGELOG.md
+extra-source-files:  CHANGELOG.md,
+                     README.md
 
 source-repository head
   type: git
