diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Justin Leitgeb
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dotenv.cabal b/dotenv.cabal
new file mode 100644
--- /dev/null
+++ b/dotenv.cabal
@@ -0,0 +1,82 @@
+name:                dotenv
+version:             0.1.0.0
+synopsis:            Loads environment variables dotenv files
+
+description:
+  .
+  In most applications,
+  <http://12factor.net/config configuration should be separated from code>.
+  While it usually works well to keep configuration in the
+  environment, there are cases where you may want to store
+  configuration in a file outside of version control.
+  .
+  "Dotenv" files have become popular for storing configuration,
+  especially in development and test environments. In
+  <https://github.com/bkeepers/dotenv Ruby>,
+  <https://github.com/theskumar/python-dotenv Python> and
+  <https://www.npmjs.com/package/dotenv Javascript> there are libraries
+  to facilitate loading of configuration options from configuration
+  files. This library loads configuration to environment variables for
+  programs written in Haskell.
+  .
+  To use, call `loadFile` from your application:
+  .
+  > import Configuration.Dotenv
+  > loadFile False "/my/dotenvfile"
+  .
+  This package also includes an executable that can be used
+  to inspect the results of applying one or more Dotenv files
+  to the environment, or for invoking your executables with
+  an environment after one or more Dotenv files is applied.
+  .
+  See the <https://github.com/stackbuilders/dotenv-hs Github>
+  page for more information on this package.
+license:             MIT
+license-file:        LICENSE
+author:              Justin Leitgeb
+maintainer:          justin@stackbuilders.com
+copyright:           2015 Stack Builders Inc.
+category:            Configuration
+build-type:          Simple
+extra-source-files:  spec/fixtures/.dotenv
+cabal-version:       >=1.10
+
+executable dotenv
+  main-is:             Main.hs
+  -- other-modules:
+  -- other-extensions:
+  build-depends:         base >=4.7 && <4.8
+                       , optparse-applicative >=0.11 && <0.12
+                       , parsec >= 3.1.0 && <= 3.2
+                       , process
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+library
+  exposed-modules:    Configuration.Dotenv.Parse
+                    , Configuration.Dotenv
+
+  build-depends:         base >=4.7 && <4.8
+                       , parsec >= 3.1.0 && <= 3.2
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+
+test-suite dotenv-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: spec, src
+  main-is: Spec.hs
+  build-depends:       base >=4.7 && <4.8
+                       , parsec >= 3.1.0 && <= 3.2
+
+                       , hspec
+
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+source-repository head
+  type:     git
+  location: https://github.com/stackbuilders/dotenv-hs
diff --git a/spec/Spec.hs b/spec/Spec.hs
new file mode 100644
--- /dev/null
+++ b/spec/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/spec/fixtures/.dotenv b/spec/fixtures/.dotenv
new file mode 100644
--- /dev/null
+++ b/spec/fixtures/.dotenv
@@ -0,0 +1,1 @@
+DOTENV=true
diff --git a/src/Configuration/Dotenv.hs b/src/Configuration/Dotenv.hs
new file mode 100644
--- /dev/null
+++ b/src/Configuration/Dotenv.hs
@@ -0,0 +1,41 @@
+module Configuration.Dotenv (load, loadFile) where
+
+import System.Environment (lookupEnv, setEnv)
+
+import Configuration.Dotenv.Parse (configParser)
+
+import Text.Parsec (parse)
+
+-- | Loads the given list of options into the environment. Optionally
+-- override existing variables with values from Dotenv files.
+load ::
+  Bool -- ^ Override existing settings?
+  -> [(String, String)] -- ^ List of values to be set in environment
+  -> IO ()
+load override = mapM_ (applySetting override)
+
+-- | Loads the options in the given file to the environment. Optionally
+-- override existing variables with values from Dotenv files.
+loadFile ::
+  Bool        -- ^ Override existing settings?
+  -> FilePath -- ^ A file containing options to load into the environment
+  -> IO ()
+loadFile override f = do
+  contents <- readFile f
+
+  case parse configParser f contents of
+    Left e        -> error $ "Failed to read file" ++ show e
+    Right options -> load override options
+
+
+applySetting :: Bool -> (String, String) -> IO ()
+applySetting override (key, value) =
+  if override then
+    setEnv key value
+
+  else do
+    res <- lookupEnv key
+
+    case res of
+      Nothing -> setEnv key value
+      Just _  -> return ()
diff --git a/src/Configuration/Dotenv/Parse.hs b/src/Configuration/Dotenv/Parse.hs
new file mode 100644
--- /dev/null
+++ b/src/Configuration/Dotenv/Parse.hs
@@ -0,0 +1,85 @@
+module Configuration.Dotenv.Parse (configParser) where
+
+import Data.Maybe (catMaybes)
+
+import Text.Parsec
+  ((<|>), many, try, lookAhead, manyTill, char, anyChar, many1)
+
+import Text.Parsec.String (Parser)
+
+import Text.ParserCombinators.Parsec.Prim (GenParser)
+
+import Text.ParserCombinators.Parsec.Char (space, newline, oneOf, noneOf)
+
+import Control.Monad (liftM2)
+
+import Text.Parsec.Combinator (eof)
+
+import Control.Applicative ((<*), (*>), (<$>))
+
+-- | Returns a parser for a Dotenv configuration file.
+-- Accepts key and value arguments separated by "=".
+-- Comments are allowed on lines by themselves and on
+-- blank lines.
+configParser :: Parser [(String, String)]
+configParser = catMaybes <$> many lineWithArguments
+
+lineWithArguments :: Parser (Maybe (String, String))
+lineWithArguments =
+  comment *> return Nothing
+  <|> newline *> return Nothing
+  <|> many1 (oneOf "\t ") *> return Nothing
+  <|> Just <$> configurationOptionWithArguments
+
+configurationOptionWithArguments :: Parser (String, String)
+configurationOptionWithArguments = do
+  _ <- many space
+
+  keyword   <- manyTill1 (noneOf "\n ") keywordArgSeparator
+
+  arguments <- argumentParser
+
+  return (keyword, arguments)
+
+argumentParser :: Parser String
+argumentParser = try quotedArgument <|> try unquotedArgument
+
+-- | Based on a commented-string parser in:
+-- http://hub.darcs.net/navilan/XMonadTasks/raw/Data/Config/Lexer.hs
+quotedWith :: Char -> Parser String
+quotedWith c =
+  char c
+  *> many chr
+  <* char c
+  where chr = esc <|> noneOf [c]
+        esc = escape *> char c
+
+quotedArgument :: Parser String
+quotedArgument = quotedWith '\'' <|> quotedWith '\"'
+
+unquotedArgument :: Parser String
+unquotedArgument =
+  many (noneOf " \t\n#") <* (try comment <|> try verticalSpace *> return () <|>
+                             lookAhead (try endOfLineOrInput))
+
+comment :: Parser ()
+comment =
+  try (many verticalSpace *> char '#')
+  *> manyTill anyChar endOfLineOrInput
+  *> return ()
+
+endOfLineOrInput :: Parser ()
+endOfLineOrInput = newline *> return () <|> eof
+
+manyTill1 :: GenParser tok st a -> GenParser tok st end -> GenParser tok st [a]
+manyTill1 p end = liftM2 (:) p (manyTill p end)
+
+keywordArgSeparator :: Parser ()
+keywordArgSeparator =
+  many verticalSpace *> char '=' *> many verticalSpace *> return ()
+
+escape :: Parser Char
+escape = char '\\'
+
+verticalSpace :: Parser Char
+verticalSpace = oneOf " \t"
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,42 @@
+module Main where
+
+import Options.Applicative
+
+import Configuration.Dotenv (loadFile)
+
+import System.Process (system)
+import System.Exit (exitWith)
+
+data Options = Options
+  { files    :: [String]
+  , program  :: String
+  , overload :: Bool
+  } deriving (Show)
+
+main :: IO ()
+main = execParser opts >>= dotEnv
+  where
+    opts = info (helper <*> config)
+      ( fullDesc
+     <> progDesc "Runs PROGRAM after loading options from FILE"
+     <> header "dotenv - loads options from dotenv files" )
+
+config :: Parser Options
+config = Options
+     <$> some (strOption (
+                  long "file"
+                  <> short 'f'
+                  <> metavar "FILE"
+                  <> help "File to read for options" ))
+
+     <*> argument str (metavar "PROGRAM")
+
+     <*> switch ( long "overload"
+                  <> short 'o'
+                  <> help "Specify this flag to override existing variables" )
+
+dotEnv :: Options -> IO ()
+dotEnv opts = do
+  mapM_ (loadFile (overload opts)) (files opts)
+  code <- system $ program opts
+  exitWith code
