diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -7,6 +7,14 @@
 It extends hadolint with the pretty-printer and EDSL for writting Dockerfiles in
 Haskell.
 
+- [Parsing files](#parsing-files)
+- [Parsing strings](#parsing-strings)
+- [Pretty-printing files](#pretty-printing-files)
+- [Writing Dockerfiles in Haskell](#writing-dockerfiles-in-haskell)
+- [Using the QuasiQuoter](#using-the-quasiquoter)
+- [Templating Dockerfiles in Haskell](#templating-dockerfiles-in-haskell)
+- [Templating Dockerfiles in Haskell](#templating-dockerfiles-in-haskell)
+
 ## Parsing files
 ```haskell
 import Language.Dockerfile
@@ -78,6 +86,27 @@
             from ("haskell" `tagged` tag)
             cabalSandboxBuild "mypackage"
         writeFile ("./examples/templating-" ++ tag ++ ".dockerfile") df
+```
+
+## Using IO in the DSL
+By default the DSL runs in the `Identity` monad. By running in IO we can
+support more features like file globbing:
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+import           Language.Dockerfile
+import qualified System.Directory     as Directory
+import qualified System.FilePath      as FilePath
+import qualified System.FilePath.Glob as Glob
+main = do
+    str <- toDockerfileStrIO $ do
+        fs <- liftIO $ do
+            cwd <- Directory.getCurrentDirectory
+            fs <- Glob.glob "./test/*.hs"
+            return (map (FilePath.makeRelative cwd) fs)
+        from "ubuntu"
+        mapM_ (\f -> add f ("/app/" ++ FilePath.takeFileName f)) fs
+    putStr str
 ```
 
 ## License
diff --git a/language-dockerfile.cabal b/language-dockerfile.cabal
--- a/language-dockerfile.cabal
+++ b/language-dockerfile.cabal
@@ -1,9 +1,9 @@
--- This file has been generated from package.yaml by hpack version 0.14.1.
+-- This file has been generated from package.yaml by hpack version 0.14.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           language-dockerfile
-version:        0.2.3.0
+version:        0.3.0.0
 synopsis:       Dockerfile linter, parser, pretty-printer and embedded DSL
 description:     Forked from @hadolint@.
                 .
@@ -57,6 +57,7 @@
     , split >=0.2
     , free
     , mtl
+    , transformers
     , template-haskell
     , th-lift
     , th-lift-instances
@@ -75,6 +76,7 @@
       Language.Dockerfile.EDSL.Quasi
       Language.Dockerfile.EDSL.Types
   other-modules:
+      Language.Dockerfile.EDSL.Plugins
       Paths_language_dockerfile
   default-language: Haskell2010
 
@@ -91,6 +93,7 @@
     , split >=0.2
     , free
     , mtl
+    , transformers
     , template-haskell
     , th-lift
     , th-lift-instances
@@ -115,6 +118,7 @@
     , split >=0.2
     , free
     , mtl
+    , transformers
     , template-haskell
     , th-lift
     , th-lift-instances
@@ -141,6 +145,7 @@
     , split >=0.2
     , free
     , mtl
+    , transformers
     , template-haskell
     , th-lift
     , th-lift-instances
@@ -151,6 +156,7 @@
       Language.Dockerfile
       Language.Dockerfile.Bash
       Language.Dockerfile.EDSL
+      Language.Dockerfile.EDSL.Plugins
       Language.Dockerfile.EDSL.Quasi
       Language.Dockerfile.EDSL.Types
       Language.Dockerfile.FormatCheck
@@ -161,6 +167,7 @@
       Language.Dockerfile.Rules
       Language.Dockerfile.Syntax
       Language.Dockerfile.Syntax.Lift
+      Language.Dockerfile.EDSL.PluginsSpec
       Language.Dockerfile.EDSL.QuasiSpec
       Language.Dockerfile.EDSLSpec
       SanitySpec
@@ -181,13 +188,18 @@
     , split >=0.2
     , free
     , mtl
+    , transformers
     , template-haskell
     , th-lift
     , th-lift-instances
     , hspec
     , QuickCheck
     , language-dockerfile
+    , Glob
+    , directory
+    , filepath
   other-modules:
+      Language.Dockerfile.EDSL.PluginsSpec
       Language.Dockerfile.EDSL.QuasiSpec
       Language.Dockerfile.EDSLSpec
       SanitySpec
diff --git a/src/Language/Dockerfile.hs b/src/Language/Dockerfile.hs
--- a/src/Language/Dockerfile.hs
+++ b/src/Language/Dockerfile.hs
@@ -12,6 +12,13 @@
       -- * Writting Dockerfiles (@Language.Dockerfile.EDSL@)
     , Language.Dockerfile.EDSL.toDockerfileStr
     , Language.Dockerfile.EDSL.toDockerfile
+
+    , Language.Dockerfile.EDSL.toDockerfileStrIO
+    , Language.Dockerfile.EDSL.toDockerfileIO
+    , Language.Dockerfile.EDSL.runDockerfileIO
+    , Language.Dockerfile.EDSL.runDockerfileStrIO
+    , Control.Monad.IO.Class.liftIO
+
     , Language.Dockerfile.EDSL.from
 
       -- ** Constructing base images
@@ -74,6 +81,7 @@
     )
   where
 
+import qualified Control.Monad.IO.Class
 import qualified Language.Dockerfile.EDSL
 import qualified Language.Dockerfile.EDSL.Quasi
 import qualified Language.Dockerfile.EDSL.Types
diff --git a/src/Language/Dockerfile/EDSL.hs b/src/Language/Dockerfile/EDSL.hs
--- a/src/Language/Dockerfile/EDSL.hs
+++ b/src/Language/Dockerfile/EDSL.hs
@@ -8,8 +8,10 @@
 -- import           Data.List                      (isInfixOf)
 import           Control.Monad.Free
 import           Control.Monad.Free.TH
+import           Control.Monad.Identity          (Identity)
+import           Control.Monad.Trans.Free        (FreeT, iterTM, runFreeT)
 import           Control.Monad.Writer
-import           Data.ByteString                (ByteString)
+import           Data.ByteString                 (ByteString)
 
 import qualified Language.Dockerfile.PrettyPrint as PrettyPrint
 import qualified Language.Dockerfile.Syntax      as Syntax
@@ -17,6 +19,7 @@
 import           Language.Dockerfile.EDSL.Types
 
 type EInstructionM = Free EInstruction
+type EInstructionTM = FreeT EInstruction
 
 makeFree ''EInstruction
 
@@ -24,33 +27,46 @@
     :: (MonadWriter [Syntax.Instruction] m)
     => EInstructionM a -> m a
 runDockerWriter = iterM runD
-  where
-    runDef f a n = tell [ f a ] >> n
-    runDef2 f a b n = tell [ f a b ] >> n
-    runD (From bi n) = case bi of
-        EUntaggedImage bi' -> runDef Syntax.From (Syntax.UntaggedImage bi') n
-        ETaggedImage bi' tg -> runDef Syntax.From (Syntax.TaggedImage bi' tg) n
-        EDigestedImage bi' d -> runDef Syntax.From (Syntax.DigestedImage bi' d) n
-    runD (CmdArgs as n) = runDef Syntax.Cmd as n
-    runD (Add s d n) = runDef2 Syntax.Add s d n
-    runD (User u n) = runDef Syntax.User u n
-    runD (Label ps n) = runDef Syntax.Label ps n
-    runD (StopSignal s n) = runDef Syntax.Stopsignal s n
-    runD (Copy s d n) = runDef2 Syntax.Copy s d n
-    runD (RunArgs as n) = runDef Syntax.Run as n
-    runD (Workdir d n) = runDef Syntax.Workdir d n
-    runD (Expose ps n) = runDef Syntax.Expose ps n
-    runD (Volume v n) = runDef Syntax.Volume v n
-    runD (EntrypointArgs e n) = runDef Syntax.Entrypoint e n
-    runD (Maintainer m n) = runDef Syntax.Maintainer m n
-    runD (Env ps n) = runDef Syntax.Env ps n
-    runD (Arg s n) = runDef Syntax.Arg s n
-    runD (Comment c n) = runDef Syntax.Comment c n
-    runD (OnBuildRaw i n) = runDef Syntax.OnBuild i n
-    runD (Embed is n) = do
-        tell (map Syntax.instruction is)
-        n
 
+runDockerWriterIO ::
+    ( Monad m
+    , MonadTrans t
+    , Monad (t m)
+    , MonadWriter [Syntax.Instruction] (t m)
+    , MonadIO (t m)
+    ) => EInstructionTM m a -> t m a
+runDockerWriterIO = iterTM runD
+
+runDef :: MonadWriter [t] m => (t1 -> t) -> t1 -> m b -> m b
+runDef f a n = tell [ f a ] >> n
+runDef2 :: MonadWriter [t] m => (t1 -> t2 -> t) -> t1 -> t2 -> m b -> m b
+runDef2 f a b n = tell [ f a b ] >> n
+
+runD :: MonadWriter [Syntax.Instruction] m => EInstruction (m b) -> m b
+runD (From bi n) = case bi of
+    EUntaggedImage bi' -> runDef Syntax.From (Syntax.UntaggedImage bi') n
+    ETaggedImage bi' tg -> runDef Syntax.From (Syntax.TaggedImage bi' tg) n
+    EDigestedImage bi' d -> runDef Syntax.From (Syntax.DigestedImage bi' d) n
+runD (CmdArgs as n) = runDef Syntax.Cmd as n
+runD (Add s d n) = runDef2 Syntax.Add s d n
+runD (User u n) = runDef Syntax.User u n
+runD (Label ps n) = runDef Syntax.Label ps n
+runD (StopSignal s n) = runDef Syntax.Stopsignal s n
+runD (Copy s d n) = runDef2 Syntax.Copy s d n
+runD (RunArgs as n) = runDef Syntax.Run as n
+runD (Workdir d n) = runDef Syntax.Workdir d n
+runD (Expose ps n) = runDef Syntax.Expose ps n
+runD (Volume v n) = runDef Syntax.Volume v n
+runD (EntrypointArgs e n) = runDef Syntax.Entrypoint e n
+runD (Maintainer m n) = runDef Syntax.Maintainer m n
+runD (Env ps n) = runDef Syntax.Env ps n
+runD (Arg s n) = runDef Syntax.Arg s n
+runD (Comment c n) = runDef Syntax.Comment c n
+runD (OnBuildRaw i n) = runDef Syntax.OnBuild i n
+runD (Embed is n) = do
+    tell (map Syntax.instruction is)
+    n
+
 instructionPos :: Syntax.Instruction -> Syntax.InstructionPos
 instructionPos i = Syntax.InstructionPos i "" 0
 
@@ -61,7 +77,7 @@
     let (_, w) = runWriter (runDockerWriter e)
     in map instructionPos w
 
--- | Runs the Dockerfile EDSL and returns a 'String' using
+-- | runs the Dockerfile EDSL and returns a 'String' using
 -- 'Language.Dockerfile.PrettyPrint'
 --
 -- @
@@ -114,12 +130,22 @@
   -> m ()
 onBuild b = mapM_ (onBuildRaw . Syntax.instruction) (toDockerfile b)
 
--- dockerIgnore :: String -> IO ()
--- dockerIgnore str = do
---     let str' = str ++ "\n"
---     -- TODO - Use projectroot
---     di <- readFile "./.dockerignore" `catch` (\(SomeException _) -> return "")
---     unless (str' `isInfixOf` di) $
---         appendFile "./.dockerignore" str'
--- dockerBuild :: FilePath -> IO ()
--- dockerBuild _dir = undefined
+-- | A version of 'toDockerfile' which allows IO actions
+toDockerfileIO :: MonadIO m => EInstructionTM m t -> m Syntax.Dockerfile
+toDockerfileIO e = liftM snd (runDockerfileIO e)
+
+-- | A version of 'toDockerfileStr' which allows IO actions
+toDockerfileStrIO :: MonadIO m => EInstructionTM m t -> m String
+toDockerfileStrIO e = liftM snd (runDockerfileStrIO e)
+
+-- | Just runs the EDSL's writer monad
+runDockerfileIO :: MonadIO m => EInstructionTM m t -> m (t, Syntax.Dockerfile)
+runDockerfileIO e = do
+    (r, w) <- runWriterT (runDockerWriterIO e)
+    return (r, map instructionPos w)
+
+-- | Runs the EDSL's writer monad and pretty-prints the result
+runDockerfileStrIO :: MonadIO m => EInstructionTM m t -> m (t, String)
+runDockerfileStrIO e = do
+    (r, w) <- runDockerfileIO e
+    return (r, PrettyPrint.prettyPrint w)
diff --git a/src/Language/Dockerfile/EDSL/Plugins.hs b/src/Language/Dockerfile/EDSL/Plugins.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Dockerfile/EDSL/Plugins.hs
@@ -0,0 +1,5 @@
+module Language.Dockerfile.EDSL.Plugins
+  where
+
+import           Language.Dockerfile.EDSL
+
diff --git a/test/Language/Dockerfile/EDSL/PluginsSpec.hs b/test/Language/Dockerfile/EDSL/PluginsSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Language/Dockerfile/EDSL/PluginsSpec.hs
@@ -0,0 +1,19 @@
+module Language.Dockerfile.EDSL.PluginsSpec
+  where
+
+-- import           Language.Dockerfile.EDSL
+-- import           Language.Dockerfile.EDSL.Plugins
+import           Test.Hspec
+
+spec =
+    describe "listPlugins" $
+        it "lists docker images matching language-dockerfile-*" pending
+            -- str <- toDockerFileStrIO $ do
+            --     ds <- liftIO (glob "./test/*.hs")
+            --     from "ubuntu"
+            --     mapM_ add ds
+            -- str `shouldBe` unlines [ "FROM ubuntu"
+            --                        , "ADD Spec.hs"
+            --                        , "ADD SanitySpec.hs"
+            --                        , "ADD Test.hs"
+            --                        ]
diff --git a/test/Language/Dockerfile/EDSLSpec.hs b/test/Language/Dockerfile/EDSLSpec.hs
--- a/test/Language/Dockerfile/EDSLSpec.hs
+++ b/test/Language/Dockerfile/EDSLSpec.hs
@@ -1,9 +1,13 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Language.Dockerfile.EDSLSpec where
 
+import           Control.Monad.IO.Class
 import           Language.Dockerfile.EDSL
 import           Language.Dockerfile.PrettyPrint
 import qualified Language.Dockerfile.Syntax      as Syntax
+import           System.Directory
+import           System.FilePath
+import           System.FilePath.Glob
 import           Test.Hspec
 
 spec :: Spec
@@ -40,3 +44,19 @@
                                  ]
 
         it "onBuild disallows unallowed instructions" pending
+
+    describe "toDockerfileStrIO" $
+        it "let's us run in the IO monad" $ do
+            -- TODO - "glob" is a really useful combinator
+            str <- toDockerfileStrIO $ do
+                fs <- liftIO $ do
+                    cwd <- getCurrentDirectory
+                    fs <- glob "./test/*.hs"
+                    return (map (makeRelative cwd) fs)
+                from "ubuntu"
+                mapM_ (\f -> add f ("/app/" ++ takeFileName f)) fs
+            str `shouldBe` unlines [ "FROM ubuntu"
+                                   , "ADD ./test/SanitySpec.hs /app/SanitySpec.hs"
+                                   , "ADD ./test/Spec.hs /app/Spec.hs"
+                                   , "ADD ./test/Test.hs /app/Test.hs"
+                                   ]
