diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,5 @@
+# Sydtest License
+
+Copyright (c) 2021 Tom Sydney Kerckhove
+
+See the Sydtest License at https://github.com/NorfairKing/sydtest/blob/master/sydtest/LICENSE.md for the full license text.
diff --git a/src/Test/Syd/Redis.hs b/src/Test/Syd/Redis.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Syd/Redis.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+
+-- TODO possibly supply a variant of 'redisSpec' that uses a different database scope per test
+-- so that the tests can still happen in parallel:
+-- see https://hackage.haskell.org/package/hedis-0.14.2/docs/Database-Redis.html#v:select
+-- and connectDatabase:
+-- https://hackage.haskell.org/package/hedis-0.14.2/docs/Database-Redis.html#t:ConnectInfo
+module Test.Syd.Redis
+  ( redisSpec,
+    redisConnectionSetupFunc,
+    checkedConnectSetupFunc,
+    RedisServerHandle (..),
+    redisServerSpec,
+    cleanRedisServerState,
+    redisServerSetupFunc,
+    redisServerSetupFunc',
+  )
+where
+
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Database.Redis as Redis
+import Network.Socket
+import Network.Socket.Free
+import qualified Network.Socket.Wait as Socket
+import Path
+import Path.IO
+import System.Process.Typed
+import Test.Syd
+import Test.Syd.Path
+import Test.Syd.Process.Typed
+
+-- | A handle to a child process that is a Redis server.
+data RedisServerHandle = RedisServerHandle
+  { redisServerHandleProcessHandle :: !(Process () () ()),
+    redisServerHandlePort :: !PortNumber
+  }
+
+-- | Run a redis server around a group of test and provide a clean connection to every test
+--
+-- Example usage:
+--
+-- >  redisSpec $ do
+-- >    it "sets up and tears down a redis connection nicely" $ \conn -> do
+-- >      runRedis conn $ do
+-- >        errOrStatus <- Redis.set "hello" "world"
+-- >        liftIO $ case errOrStatus of
+-- >          Left err -> expectationFailure $ show err
+-- >          Right status -> status `shouldBe` Ok
+-- >        errOrReply <- Redis.get "hello"
+-- >        liftIO $ case errOrReply of
+-- >          Left err -> expectationFailure $ show err
+-- >          Right val -> val `shouldBe` Just "world"
+--
+-- This function just combines 'redisServerSpec' with 'setupAroundWith' redisConnectionSetupFunc'.
+redisSpec :: TestDefM (RedisServerHandle ': outers) Redis.Connection result -> TestDefM outers inner result
+redisSpec = redisServerSpec . setupAroundWith' (\serverHandle _ -> redisConnectionSetupFunc serverHandle)
+
+-- | Set up a clean redis connection given a handle to the redis server.
+--
+-- This function cleans the state using `flushall`.
+redisConnectionSetupFunc :: RedisServerHandle -> SetupFunc Redis.Connection
+redisConnectionSetupFunc RedisServerHandle {..} = do
+  let connInfo = Redis.defaultConnectInfo {connectPort = PortNumber redisServerHandlePort}
+  conn <- checkedConnectSetupFunc connInfo
+  SetupFunc $ \func -> do
+    cleanRedisServerState conn
+    func conn
+
+-- | A 'SetupFunc' that 'bracket's 'checkedConnect' and 'disconnect'.
+checkedConnectSetupFunc :: Redis.ConnectInfo -> SetupFunc Redis.Connection
+checkedConnectSetupFunc connInfo = bracketSetupFunc (checkedConnect connInfo) disconnect
+
+-- | Run a redis server around a group of tests.
+redisServerSpec :: TestDefM (RedisServerHandle ': outers) inner result -> TestDefM outers inner result
+redisServerSpec = setupAroundAll redisServerSetupFunc . sequential -- Must run sequentially because state is shared.
+
+-- | Clean the redis server's state.
+cleanRedisServerState :: Connection -> IO ()
+cleanRedisServerState conn = do
+  errOrStatus <- runRedis conn Redis.flushall -- Clean state
+  case errOrStatus of
+    Left err -> expectationFailure $ "Something went wrong while trying to clean the state before the test starts: " <> show err
+    Right s -> s `shouldBe` Ok
+
+-- | Setup func for running a Redis server
+--
+-- This function uses a temporary directory (using 'tempDirSetupFunc') for any state.
+redisServerSetupFunc :: SetupFunc RedisServerHandle
+redisServerSetupFunc = do
+  td <- tempDirSetupFunc "sydtest-hedis"
+  redisServerSetupFunc' td
+
+-- | Setup func for running a Redis server in a given directory
+redisServerSetupFunc' :: Path Abs Dir -> SetupFunc RedisServerHandle
+redisServerSetupFunc' td = do
+  pidFile <- resolveFile td "redis.pid"
+  logFile <- resolveFile td "redis.log"
+  portInt <- liftIO $ do
+    (portInt, _socket) <- openFreePort
+    close _socket
+    pure portInt
+  let pn = fromIntegral portInt -- (hopefully) safe because it came from 'getFreePort'.
+  let configFileContents =
+        T.pack $
+          unlines
+            [ unwords ["port", show (fromIntegral pn :: Int)],
+              unwords ["pidfile", fromAbsFile pidFile],
+              unwords ["always-show-logo", "no"], -- No need to see the logo.
+              unwords ["logfile", fromAbsFile logFile]
+            ]
+  configFile <- tempBinaryFileWithContentsSetupFunc "config-file" (TE.encodeUtf8 configFileContents)
+  let pc =
+        setWorkingDir (fromAbsDir td) $
+          setStdout inherit $
+            setStderr inherit $
+              proc
+                "redis-server"
+                [fromAbsFile configFile]
+  ph <- typedProcessSetupFunc pc
+  liftIO $ Socket.wait "127.0.0.1" portInt
+  pure $
+    RedisServerHandle
+      { redisServerHandleProcessHandle = ph,
+        redisServerHandlePort = pn
+      }
diff --git a/sydtest-hedis.cabal b/sydtest-hedis.cabal
new file mode 100644
--- /dev/null
+++ b/sydtest-hedis.cabal
@@ -0,0 +1,70 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.4.
+--
+-- see: https://github.com/sol/hpack
+
+name:           sydtest-hedis
+version:        0.0.0.0
+synopsis:       An hedis companion library for sydtest
+category:       Testing
+homepage:       https://github.com/NorfairKing/sydtest#readme
+bug-reports:    https://github.com/NorfairKing/sydtest/issues
+author:         Tom Sydney Kerckhove
+maintainer:     syd@cs-syd.eu
+copyright:      Copyright (c) 2021 Tom Sydney Kerckhove
+license:        OtherLicense
+license-file:   LICENSE.md
+build-type:     Simple
+
+source-repository head
+  type: git
+  location: https://github.com/NorfairKing/sydtest
+
+flag sydtest_integration_tests
+  description: Whether to allow building integration tests
+  manual: False
+  default: True
+
+library
+  exposed-modules:
+      Test.Syd.Redis
+  other-modules:
+      Paths_sydtest_hedis
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , hedis
+    , network
+    , path
+    , path-io
+    , port-utils
+    , sydtest
+    , sydtest-typed-process
+    , text
+    , typed-process
+  default-language: Haskell2010
+
+test-suite sydtest-hedis-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Test.Syd.RedisSpec
+      Paths_sydtest_hedis
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
+  build-tool-depends:
+      sydtest-discover:sydtest-discover
+  build-depends:
+      base >=4.7 && <5
+    , hedis
+    , sydtest
+    , sydtest-hedis
+  if flag(sydtest_integration_tests)
+    buildable: True
+  else
+    buildable: False
+  default-language: Haskell2010
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF sydtest-discover #-}
diff --git a/test/Test/Syd/RedisSpec.hs b/test/Test/Syd/RedisSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Syd/RedisSpec.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Test.Syd.RedisSpec (spec) where
+
+import Database.Redis as Redis
+import Test.Syd
+import Test.Syd.Redis
+
+spec :: Spec
+spec = do
+  describe "redisServerSpec" $
+    redisServerSpec $
+      it "sets up and tears down the redis server nicely" $ do
+        pure () :: IO ()
+  describe "redisSpec" $
+    redisSpec $ do
+      it "sets up and tears down a redis connection nicely" $ \conn -> do
+        runRedis conn $ do
+          errOrStatus <- Redis.set "hello" "world"
+          liftIO $ case errOrStatus of
+            Left err -> expectationFailure $ show err
+            Right status -> status `shouldBe` Ok
+          errOrReply <- Redis.get "hello"
+          liftIO $ case errOrReply of
+            Left err -> expectationFailure $ show err
+            Right val -> val `shouldBe` Just "world"
+      doNotRandomiseExecutionOrder $
+        describe "shared state" $ do
+          it "can write a message" $ \conn -> runRedis conn $ do
+            errOrStatus <- Redis.set "hello" "world"
+            liftIO $ case errOrStatus of
+              Left err -> expectationFailure $ show err
+              Right status -> status `shouldBe` Ok
+          it "cannot read a message that has not been written yet" $ \conn -> runRedis conn $ do
+            errOrReply <- Redis.get "hello"
+            liftIO $ case errOrReply of
+              Left err -> expectationFailure $ show err
+              Right Nothing -> pure ()
+              Right (Just val) -> expectationFailure $ "Should not have been able to read anything, but got: " <> show val
