diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2020, Tobias Dammers
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Tobias Dammers nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/migrant-sqlite-simple.cabal b/migrant-sqlite-simple.cabal
new file mode 100644
--- /dev/null
+++ b/migrant-sqlite-simple.cabal
@@ -0,0 +1,44 @@
+cabal-version: 2.4
+-- Initial package description 'migrant.cabal' generated by 'cabal init'.
+-- For further documentation, see http://haskell.org/cabal/users-guide/
+
+name: migrant-sqlite-simple
+version: 0.1.0.0
+synopsis: Semi-automatic database schema migrations
+-- description:
+homepage: https://github.com/tdammers/migrant
+-- bug-reports:
+license: BSD-3-Clause
+license-file: LICENSE
+author: Tobias Dammers
+maintainer: tdammers@gmail.com
+-- copyright:
+category: Database
+extra-source-files:
+
+library
+  exposed-modules: Database.Migrant.Driver.Sqlite
+  -- other-extensions:
+  build-depends: base ^>=4.12.0.0
+               , migrant-core
+               , sqlite-simple >=0.4.18.0 && <0.5
+               , text >=1.2.4.0 && <1.3
+  hs-source-dirs: src
+  default-language: Haskell2010
+
+test-suite migrant-sqlite-simple-test
+  default-language: Haskell2010
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Spec.hs
+  other-modules: Database.Migrant.Driver.Sqlite_Tests
+  build-depends: base ^>=4.12.0.0
+               , migrant-core
+               , migrant-sqlite-simple
+               , HUnit >=1.6.1.0 && <1.7
+               , QuickCheck >=2.14.2 && <2.15
+               , sqlite-simple >=0.4.18.0 && <0.5
+               , tasty >=1.4 && <1.5
+               , tasty-hunit >=0.10.0.2 && <0.11
+               , tasty-quickcheck >=0.10.1.1 && <0.11
+               , text >=1.2.4.0 && <1.3
diff --git a/src/Database/Migrant/Driver/Sqlite.hs b/src/Database/Migrant/Driver/Sqlite.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Migrant/Driver/Sqlite.hs
@@ -0,0 +1,29 @@
+{-#LANGUAGE OverloadedStrings #-}
+module Database.Migrant.Driver.Sqlite
+where
+
+import Database.Migrant.Driver.Class
+import Database.Migrant.MigrationName
+import qualified Database.SQLite.Simple as Sqlite
+
+instance Driver Sqlite.Connection where
+  withTransaction action conn = Sqlite.withTransaction conn (action conn)
+
+  initMigrations conn =
+    Sqlite.execute_ conn
+      "CREATE TABLE IF NOT EXISTS _migrations (id INTEGER PRIMARY KEY, name TEXT)"
+
+  markUp name conn =
+    Sqlite.execute conn
+      "INSERT INTO _migrations (name) VALUES (?)"
+      [unpackMigrationName name]
+
+  markDown name conn =
+    Sqlite.execute conn
+      "DELETE FROM _migrations WHERE name = ?"
+      [unpackMigrationName name]
+
+  getMigrations conn = do
+    result <- Sqlite.query_ conn
+      "SELECT name FROM _migrations ORDER BY id"
+    return [ MigrationName name | Sqlite.Only name <- result ]
diff --git a/test/Database/Migrant/Driver/Sqlite_Tests.hs b/test/Database/Migrant/Driver/Sqlite_Tests.hs
new file mode 100644
--- /dev/null
+++ b/test/Database/Migrant/Driver/Sqlite_Tests.hs
@@ -0,0 +1,68 @@
+{-#LANGUAGE OverloadedStrings #-}
+module Database.Migrant.Driver.Sqlite_Tests
+( tests
+)
+where
+
+import Test.Tasty
+import Test.Tasty.QuickCheck
+import Test.Tasty.HUnit
+import Data.Text (Text)
+
+import Database.Migrant
+import Database.Migrant.Driver.Sqlite
+import qualified Database.SQLite.Simple as Sqlite
+
+tests :: TestTree
+tests = testGroup "SQLite"
+  [ testCase "Setup/init" testSqliteSetup
+  , testCase "Up" testSqliteUp
+  , testCase "Down" testSqliteDown
+  ]
+
+testSqliteSetup :: Assertion
+testSqliteSetup = do
+  conn <- Sqlite.open ":memory:"
+  migrate [] undefined undefined conn
+  actual <- Sqlite.query_ conn "SELECT name FROM _migrations ORDER BY id"
+  let expected = [] :: [Sqlite.Only Text]
+  assertEqual "Migration table exists" expected actual
+
+testSqliteUp :: Assertion
+testSqliteUp = do
+  let up "foo" conn =
+        Sqlite.execute_ conn "CREATE TABLE foo (id INTEGER PRIMARY KEY)"
+      up name _ = error ("Invalid name " <> show name)
+      down "foo" conn =
+        Sqlite.execute_ conn "DROP TABLE foo"
+      down name _ = error ("Invalid name " <> show name)
+  conn <- Sqlite.open ":memory:"
+  migrate ["foo"] up down conn
+
+  actual <- Sqlite.query_ conn "SELECT name FROM _migrations ORDER BY id"
+  let expected = [Sqlite.Only ("foo" :: Text)]
+  assertEqual "Up migration logged" expected actual
+
+  actual <- Sqlite.query_ conn "SELECT COUNT(1) FROM sqlite_master WHERE type = 'table' AND name = 'foo'"
+  let expected = [Sqlite.Only (1 :: Int)]
+  assertEqual "Up migration happened" expected actual
+
+testSqliteDown :: Assertion
+testSqliteDown = do
+  let up "foo" conn =
+        Sqlite.execute_ conn "CREATE TABLE foo (id INTEGER PRIMARY KEY)"
+      up name _ = error ("Invalid name " <> show name)
+      down "foo" conn =
+        Sqlite.execute_ conn "DROP TABLE foo"
+      down name _ = error ("Invalid name " <> show name)
+  conn <- Sqlite.open ":memory:"
+  migrate ["foo"] up down conn
+  migrate [] up down conn
+
+  actual <- Sqlite.query_ conn "SELECT name FROM _migrations ORDER BY id"
+  let expected = [] :: [Sqlite.Only Text]
+  assertEqual "Down migration logged" expected actual
+
+  actual <- Sqlite.query_ conn "SELECT COUNT(1) FROM sqlite_master WHERE type = 'table' AND name = 'foo'"
+  let expected = [Sqlite.Only (0 :: Int)]
+  assertEqual "Down migration happened" expected actual
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,16 @@
+module Main (main) where
+
+import Test.Tasty
+import Test.Tasty.QuickCheck
+import Test.Tasty.HUnit
+
+import Data.Text (Text)
+import qualified Data.Text as Text
+
+import qualified Database.Migrant.Driver.Sqlite_Tests
+
+main :: IO ()
+main = defaultMain $
+  testGroup "Migrant"
+    [ Database.Migrant.Driver.Sqlite_Tests.tests
+    ]
