diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## [1.4.1] - 2023-03-31
+
+### Fixed
+
+- Open Nix database in read-only mode instead of immutable.
+
 ## [1.4] - 2023-03-26
 
 ### Changed
diff --git a/cachix.cabal b/cachix.cabal
--- a/cachix.cabal
+++ b/cachix.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               cachix
-version:            1.4
+version:            1.4.1
 license:            Apache-2.0
 license-file:       LICENSE
 copyright:          2018 Domen Kozar
diff --git a/src/Cachix/Client/Servant.hs b/src/Cachix/Client/Servant.hs
--- a/src/Cachix/Client/Servant.hs
+++ b/src/Cachix/Client/Servant.hs
@@ -1,9 +1,6 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# OPTIONS_GHC -O0 #-}
-
--- TODO https://github.com/haskell-servant/servant/issues/986
 
 module Cachix.Client.Servant
   ( isErr,
diff --git a/src/Cachix/Client/Store.hs b/src/Cachix/Client/Store.hs
--- a/src/Cachix/Client/Store.hs
+++ b/src/Cachix/Client/Store.hs
@@ -1,7 +1,25 @@
 {-# LANGUAGE OverloadedStrings #-}
 
-module Cachix.Client.Store (withStore, Store, PathInfo (..), StorePath (..), base16to32, computeClosure, queryPathInfo, followLinksToStorePath, getStorePathHash, getStorePathBaseName, getPath) where
+module Cachix.Client.Store
+  ( -- * Opening a store
+    LocalStoreOptions (..),
+    withLocalStore,
+    withStore,
+    Store,
 
+    -- * Working store contents
+    PathInfo (..),
+    StorePath (..),
+    base16to32,
+    computeClosure,
+    queryPathInfo,
+    followLinksToStorePath,
+    getStorePathHash,
+    getStorePathBaseName,
+    getPath,
+  )
+where
+
 import Cachix.Client.ProcessGraph (processGraph)
 import Data.ByteArray.Encoding (Base (..), convertFromBase)
 import qualified Data.Set as Set
@@ -35,21 +53,47 @@
   let storePath' = T.drop (T.length prefix) (toS storePath)
   return $ toS $ prefix <> T.intercalate "/" (take 3 $ T.splitOn "/" storePath')
 
-withStore :: Text -> (Store -> IO ()) -> IO ()
-withStore storePrefix =
+-- | Options for `withLocalStore`.
+data LocalStoreOptions = LocalStoreOptions
+  { -- | The path to the Nix store directory. Typically: @"/nix"@
+    storePrefix :: !Text,
+    -- | Whether to use SQLite Write-Ahead Logging (WAL) mode.
+    --
+    -- This needs to match the ambient configuration, because otherwise, the db
+    -- may be corrupted: https://github.com/cachix/cachix/issues/475
+    useSqliteWAL :: !Bool
+  }
+
+-- | Run an 'IO' action while retaining a 'Store' resource for the duration of the action.
+--
+-- This does not rely on the @nix@ command being available.
+withLocalStore :: LocalStoreOptions -> (Store -> IO a) -> IO a
+withLocalStore opts =
   bracket open close
   where
-    uri = "file:" <> toS storePrefix <> "/var/nix/db/db.sqlite?immutable=1"
-    flags = [SQLite.SQLOpenReadOnly, SQLite.SQLOpenURI]
+    uri = toS (storePrefix opts) <> "/var/nix/db/db.sqlite"
+    flags = [SQLite.SQLOpenReadOnly]
     close (Store _ db) = SQLite.close db
+    vfs =
+      if useSqliteWAL opts
+        then SQLite.SQLVFSDefault
+        else SQLite.SQLVFSUnixDotFile
     open = do
-      (_, out, _) <- readProcessWithExitCode "nix" ["show-config", "--extra-experimental-features", "nix-command"] mempty
-      let vfs =
-            if "use-sqlite-wal = false" `T.isInfixOf` toS out
-              then SQLite.SQLVFSUnixDotFile
-              else SQLite.SQLVFSDefault
       conn <- SQLite.open2 uri flags vfs
-      return $ Store storePrefix conn
+      return $ Store (storePrefix opts) conn
+
+-- | 'withLocalStore' but infers 'useSqliteWAL' from the @nix show-config@ command.
+withStore :: Text -> (Store -> IO a) -> IO a
+withStore storePrefix_ f = do
+  useWAL <- do
+    (_, out, _) <- readProcessWithExitCode "nix" ["show-config", "--extra-experimental-features", "nix-command"] mempty
+    pure (not ("use-sqlite-wal = false" `T.isInfixOf` toS out))
+  withLocalStore
+    LocalStoreOptions
+      { storePrefix = storePrefix_,
+        useSqliteWAL = useWAL
+      }
+    f
 
 queryNarinfo :: Text
 queryNarinfo = "select id, hash, deriver, narSize from ValidPaths where path = :path"
