diff --git a/.scala-steward.conf b/.scala-steward.conf
index 91bf2eaf..588e5b52 100644
--- a/.scala-steward.conf
+++ b/.scala-steward.conf
@@ -1 +1,7 @@
-updates.ignore = [ { groupId = "org.apache.poi" } ]
\ No newline at end of file
+updates.ignore = [
+  { groupId = "org.apache.poi" },
+]
+
+updates.pin = [
+  { groupId = "co.fs2", version = "2." }
+]
\ No newline at end of file
diff --git a/modules/analysis/src/main/scala/docspell/analysis/TextAnalyser.scala b/modules/analysis/src/main/scala/docspell/analysis/TextAnalyser.scala
index 2b84c4e2..7f52fd44 100644
--- a/modules/analysis/src/main/scala/docspell/analysis/TextAnalyser.scala
+++ b/modules/analysis/src/main/scala/docspell/analysis/TextAnalyser.scala
@@ -37,7 +37,7 @@ object TextAnalyser {
       blocker: Blocker
   ): Resource[F, TextAnalyser[F]] =
     Resource
-      .liftF(Nlp(cfg.nlpConfig))
+      .eval(Nlp(cfg.nlpConfig))
       .map(stanfordNer =>
         new TextAnalyser[F] {
           def annotate(
diff --git a/modules/analysis/src/main/scala/docspell/analysis/nlp/PipelineCache.scala b/modules/analysis/src/main/scala/docspell/analysis/nlp/PipelineCache.scala
index 3b38da22..99657826 100644
--- a/modules/analysis/src/main/scala/docspell/analysis/nlp/PipelineCache.scala
+++ b/modules/analysis/src/main/scala/docspell/analysis/nlp/PipelineCache.scala
@@ -47,8 +47,8 @@ object PipelineCache {
     def obtain(key: String, settings: NlpSettings): Resource[F, Annotator[F]] =
       for {
         _  <- cacheClear.withCache
-        id <- Resource.liftF(makeSettingsId(settings))
-        nlp <- Resource.liftF(
+        id <- Resource.eval(makeSettingsId(settings))
+        nlp <- Resource.eval(
           data.modify(cache => getOrCreate(key, id, cache, settings, creator))
         )
       } yield nlp
diff --git a/modules/joex/src/main/scala/docspell/joex/JoexServer.scala b/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
index 9296ee30..10db220c 100644
--- a/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
+++ b/modules/joex/src/main/scala/docspell/joex/JoexServer.scala
@@ -28,8 +28,8 @@ object JoexServer {
   )(implicit T: Timer[F]): Stream[F, Nothing] = {
 
     val app = for {
-      signal   <- Resource.liftF(SignallingRef[F, Boolean](false))
-      exitCode <- Resource.liftF(Ref[F].of(ExitCode.Success))
+      signal   <- Resource.eval(SignallingRef[F, Boolean](false))
+      exitCode <- Resource.eval(Ref[F].of(ExitCode.Success))
       joexApp <-
         JoexAppImpl
           .create[F](cfg, signal, pools.connectEC, pools.httpClientEC, pools.blocker)
diff --git a/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala b/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala
index 56e48012..47f9cdb4 100644
--- a/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala
+++ b/modules/joex/src/main/scala/docspell/joex/analysis/RegexNerFile.scala
@@ -38,7 +38,7 @@ object RegexNerFile {
   ): Resource[F, RegexNerFile[F]] =
     for {
       dir    <- File.withTempDir[F](cfg.directory, "regexner-")
-      writer <- Resource.liftF(Semaphore(1))
+      writer <- Resource.eval(Semaphore(1))
     } yield new Impl[F](cfg.copy(directory = dir), blocker, store, writer)
 
   final private class Impl[F[_]: Concurrent: ContextShift](
diff --git a/modules/joex/src/main/scala/docspell/joex/scheduler/PeriodicScheduler.scala b/modules/joex/src/main/scala/docspell/joex/scheduler/PeriodicScheduler.scala
index 6b406861..cbc7ec22 100644
--- a/modules/joex/src/main/scala/docspell/joex/scheduler/PeriodicScheduler.scala
+++ b/modules/joex/src/main/scala/docspell/joex/scheduler/PeriodicScheduler.scala
@@ -40,8 +40,8 @@ object PeriodicScheduler {
       timer: Timer[F]
   ): Resource[F, PeriodicScheduler[F]] =
     for {
-      waiter <- Resource.liftF(SignallingRef(true))
-      state  <- Resource.liftF(SignallingRef(PeriodicSchedulerImpl.emptyState[F]))
+      waiter <- Resource.eval(SignallingRef(true))
+      state  <- Resource.eval(SignallingRef(PeriodicSchedulerImpl.emptyState[F]))
       psch = new PeriodicSchedulerImpl[F](
         cfg,
         sch,
@@ -52,7 +52,7 @@ object PeriodicScheduler {
         state,
         timer
       )
-      _ <- Resource.liftF(psch.init)
+      _ <- Resource.eval(psch.init)
     } yield psch
 
 }
diff --git a/modules/joex/src/main/scala/docspell/joex/scheduler/SchedulerBuilder.scala b/modules/joex/src/main/scala/docspell/joex/scheduler/SchedulerBuilder.scala
index 50ed7a17..1a804b55 100644
--- a/modules/joex/src/main/scala/docspell/joex/scheduler/SchedulerBuilder.scala
+++ b/modules/joex/src/main/scala/docspell/joex/scheduler/SchedulerBuilder.scala
@@ -46,9 +46,9 @@ case class SchedulerBuilder[F[_]: ConcurrentEffect: ContextShift](
   def resource: Resource[F, Scheduler[F]] = {
     val scheduler = for {
       jq     <- queue
-      waiter <- Resource.liftF(SignallingRef(true))
-      state  <- Resource.liftF(SignallingRef(SchedulerImpl.emptyState[F]))
-      perms  <- Resource.liftF(Semaphore(config.poolSize.toLong))
+      waiter <- Resource.eval(SignallingRef(true))
+      state  <- Resource.eval(SignallingRef(SchedulerImpl.emptyState[F]))
+      perms  <- Resource.eval(Semaphore(config.poolSize.toLong))
     } yield new SchedulerImpl[F](
       config,
       blocker,
diff --git a/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
index b42592ac..3a7aba3e 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
@@ -158,7 +158,7 @@ object TemplateRoutes {
 
   private def memo[F[_]: Sync, A](fa: => F[A]): F[A] = {
     val ref = new AtomicReference[A]()
-    Sync[F].suspend {
+    Sync[F].defer {
       Option(ref.get) match {
         case Some(a) => a.pure[F]
         case None =>
diff --git a/modules/store/src/main/scala/docspell/store/Store.scala b/modules/store/src/main/scala/docspell/store/Store.scala
index 49a25c91..d20c78ef 100644
--- a/modules/store/src/main/scala/docspell/store/Store.scala
+++ b/modules/store/src/main/scala/docspell/store/Store.scala
@@ -42,7 +42,7 @@ object Store {
     for {
       xa <- hxa
       st = new StoreImpl[F](jdbc, xa)
-      _ <- Resource.liftF(st.migrate)
+      _ <- Resource.eval(st.migrate)
     } yield st
   }
 }
diff --git a/modules/store/src/test/scala/docspell/store/StoreFixture.scala b/modules/store/src/test/scala/docspell/store/StoreFixture.scala
index 839eb242..35b5ae68 100644
--- a/modules/store/src/test/scala/docspell/store/StoreFixture.scala
+++ b/modules/store/src/test/scala/docspell/store/StoreFixture.scala
@@ -62,6 +62,6 @@ object StoreFixture {
     for {
       xa <- makeXA(jdbc)
       store = new StoreImpl[IO](jdbc, xa)
-      _ <- Resource.liftF(store.migrate)
+      _ <- Resource.eval(store.migrate)
     } yield store
 }
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 4a8cc41a..f1067a16 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -16,7 +16,7 @@ object Dependencies {
   val EmilVersion             = "0.7.3"
   val FlexmarkVersion         = "0.62.2"
   val FlywayVersion           = "7.7.1"
-  val Fs2Version              = "2.5.3"
+  val Fs2Version              = "2.5.4"
   val H2Version               = "1.4.200"
   val Http4sVersion           = "0.21.20"
   val Icu4jVersion            = "68.2"