diff --git a/Core/include/Core/ConditionsLoader.h b/Core/include/Core/ConditionsLoader.h
index 561b336c95e00678eba297f461c92a970e5e2280..15063786ded2812d4595e4c6ac7ebcd67da6094a 100644
--- a/Core/include/Core/ConditionsLoader.h
+++ b/Core/include/Core/ConditionsLoader.h
@@ -37,6 +37,8 @@ namespace LHCb::Detector {
   };
   using ConditionsOverrides = std::vector<ConditionOverrideEntry>;
 
+  using ConditionsOverlayExceptions = std::map<std::string, std::vector<std::string>>;
+
   class ConditionsLoader : public dd4hep::cond::ConditionsDataLoader {
     using Key    = std::pair<std::string, std::string>;
     using KeyMap = std::map<dd4hep::Condition::key_type, Key>;
@@ -59,6 +61,10 @@ namespace LHCb::Detector {
     ConditionsOverlay   m_overlay;
     ConditionsOverrides m_conditionsOverride;
 
+    // Map of filenames -> list of conditions names for which we have to ignore the overlay
+    // (and always go to the DB)
+    ConditionsOverlayExceptions m_overlayExceptions;
+
   public:
     /// Default constructor
     ConditionsLoader( dd4hep::Detector& description, dd4hep::cond::ConditionsManager mgr, const std::string& nam );
@@ -81,5 +87,9 @@ namespace LHCb::Detector {
         throw std::logic_error( "ConditionsLoader::set_limited_iov_paths invoked before initialize" );
       }
     }
+    // Set exceptions to the conditions overlay
+    void set_overlay_exceptions( ConditionsOverlayExceptions exceptions ) {
+      m_overlayExceptions = std::move( exceptions );
+    }
   };
 } // namespace LHCb::Detector
diff --git a/Core/src/ConditionsLoader.cpp b/Core/src/ConditionsLoader.cpp
index 6652c1dad96c4bffc280fc9d90db75ee2d434ddd..36ac739c313b256614521c2139314c04c2a25463 100644
--- a/Core/src/ConditionsLoader.cpp
+++ b/Core/src/ConditionsLoader.cpp
@@ -216,6 +216,14 @@ size_t LHCb::Detector::ConditionsLoader::load_many( const dd4hep::IOV& req_iov,
             }
           }
 
+          std::vector<std::string>  conditionsWithoutOverlay;
+          std::optional<YAML::Node> origData;
+          if ( auto it = m_overlayExceptions.find( filename );
+               it != m_overlayExceptions.end() && !it->second.empty() ) {
+            conditionsWithoutOverlay = it->second;
+            origData                 = YAML::Load( buffer );
+          }
+
           // prepare the store for the conditions we will register to the conditions manager
           // ... it would be nice if we had an actual type here and not a handle
           std::vector<dd4hep::Condition> entity_conditions;
@@ -223,7 +231,12 @@ size_t LHCb::Detector::ConditionsLoader::load_many( const dd4hep::IOV& req_iov,
 
           // loop over all conditions in the YAML document
           for ( const auto& cond_data : data ) {
-            const std::string cond_name = cond_data.first.as<std::string>();
+            const std::string         cond_name = cond_data.first.as<std::string>();
+            std::optional<YAML::Node> noOverlayData;
+            if ( auto it = std::find( conditionsWithoutOverlay.begin(), conditionsWithoutOverlay.end(), cond_name );
+                 it != conditionsWithoutOverlay.end() ) {
+              noOverlayData = ( *origData )[cond_name];
+            }
 
             // Check if the condition has been requested.
             // Only the upper 32 bits of kmaker.hash have been set
@@ -232,7 +245,8 @@ size_t LHCb::Detector::ConditionsLoader::load_many( const dd4hep::IOV& req_iov,
 
             auto cond_ident = conditions_by_location_hash.find( kmaker.hash ); // O[log(N)]
             if ( cond_ident != conditions_by_location_hash.end() ) {
-              register_condition( entity_conditions, *cond_ident, cond_name, cond_data.second, filename );
+              register_condition( entity_conditions, *cond_ident, cond_name,
+                                  noOverlayData ? *noOverlayData : cond_data.second, filename );
             } else {
               dd4hep::printout( dd4hep::WARNING, "ConditionsLoader", "++ Got stray condition %s from %s",
                                 cond_name.c_str(), filename.c_str() );
diff --git a/Core/src/DetectorDataService.cpp b/Core/src/DetectorDataService.cpp
index 0075bb9d16e887c05bee5e6edc97253654336cc7..f9054632a8cc348f495cd9b6c0c298481ee62a0e 100644
--- a/Core/src/DetectorDataService.cpp
+++ b/Core/src/DetectorDataService.cpp
@@ -130,11 +130,25 @@ void LHCb::Detector::DetectorDataService::initialize( const nlohmann::json& conf
     auto overlay = config["overlay"];
     if ( overlay.is_boolean() ) {
       loader["UseOverlay"] = overlay.get<bool>();
-    } else if ( overlay.is_object() && overlay.contains( "path" ) ) {
-      loader["UseOverlay"]      = true;
-      loader["OverlayInitPath"] = overlay.at( "path" ).get<std::string>();
-    } else {
-      throw std::invalid_argument( fmt::format( "invalid configuration for conditions overlay '{}'", overlay ) );
+    } else if ( overlay.is_object() ) {
+      loader["UseOverlay"] = true;
+      for ( const auto& el : overlay.items() ) {
+        if ( el.key() != "path" && el.key() != "exceptions" ) {
+          throw std::invalid_argument( fmt::format( "invalid configuration for conditions overlay '{}'", overlay ) );
+        }
+        if ( el.key() == "path" ) {
+          loader["OverlayInitPath"] = overlay["path"].get<std::string>();
+        } else if ( el.key() == "exceptions" ) {
+          // I would love to use something like
+          //   loader["OverlayExceptions"] = overlay["exceptions"].get<ConditionsOverlayExceptions>();
+          // but I didn't manage to figure out how to convince DD4hep properties to understand
+          // map<string,vector<string>>
+
+          // As explained later this is safe to do.
+          auto loader_impl = static_cast<ConditionsLoader*>( &loader );
+          loader_impl->set_overlay_exceptions( overlay["exceptions"].get<ConditionsOverlayExceptions>() );
+        }
+      }
     }
   }
   loader.initialize();
diff --git a/Core/tests/src/test_DDS_with_Overlay.cpp b/Core/tests/src/test_DDS_with_Overlay.cpp
index 2014a45a6f05144af26692e15dbf55a8ac1d3b39..1b6d99d2a64d4edaa7da6abb43834b8540226b7d 100644
--- a/Core/tests/src/test_DDS_with_Overlay.cpp
+++ b/Core/tests/src/test_DDS_with_Overlay.cpp
@@ -80,12 +80,12 @@ VPRight: !alignment
   CHECK_THAT( align.translation.Y(), Catch::Matchers::WithinRel( 5 * mm, 1.e-4 ) );
   CHECK_THAT( align.translation.Z(), Catch::Matchers::WithinRel( 7 * mm, 1.e-4 ) );
 
-  // phase 3: change the alignment paramters and update the overlay
+  // phase 3: change the alignment parameters and update the overlay
   {
     RotationZYX   rot{RotationZ{5 * deg}};
     dd4hep::Delta new_align{Position{1 * mm, 4 * mm, 2.5 * mm}, rot};
     dds.update_alignment( de.detector(), new_align );
-    // We cannot check anything here becase the overlay instance is private to the system
+    // We cannot check anything here because the overlay instance is private to the system
   }
 
   // phase 4: reload alignments (from overlay)
@@ -150,3 +150,135 @@ VPRight: !alignment
   CHECK_THAT( align.rotation.Theta(), Catch::Matchers::WithinAbs( 0, 1.e-6 ) );
   CHECK_THAT( align.rotation.Psi(), Catch::Matchers::WithinAbs( 0, 1.e-6 ) );
 }
+
+TEST_CASE( "DDS with overlay and exceptions" ) {
+  namespace fs = std::filesystem;
+
+  Detector::Test::Fixture f;
+
+  auto& description = f.description();
+
+  description.fromXML( "compact/run3/trunk/debug/VP_debug.xml" );
+  // description.fromXML( "compact/run3/trunk/LHCb.xml" );
+
+  REQUIRE( description.state() == dd4hep::Detector::READY );
+
+  auto det = description.detector( "VP" );
+  // the `!!` is needed because handles have `operator!` but not `operator bool`
+  REQUIRE( !!det );
+
+  Detector::Test::TmpDir tmp;
+
+  // phase 1: prepare the directory with the initial alignments (to fill the overlay)
+  auto cond_dir = tmp.path / "Conditions" / "VP" / "Alignment";
+  fs::create_directories( cond_dir );
+  std::ofstream( cond_dir / "Global.yml" ) << R"(---
+VPSystem: !alignment
+  position: [0.0 * mm, 5.0 * mm, 7.0 * mm]
+VPLeft: !alignment
+  position: [1.0 * mm, -2.0 * mm, 0.0 * mm]
+VPRight: !alignment
+  position: [-1.0 * mm, -2.0 * mm, 0.0 * mm]
+)";
+  REQUIRE( !fs::exists( cond_dir / "Ladders.yml" ) );
+
+  // phase 2: load conditions with the overlay filled from the injected data
+  LHCb::Detector::DetectorDataService dds( description, {"/world", "VP"} );
+  dds.initialize( nlohmann::json(
+      {{"repository", "file:tests/ConditionsIOV"},
+       {"overlay", {{"path", tmp.path}, {"exceptions", {{"Conditions/VP/Alignment/Global.yml", {"VPSystem"}}}}}}} ) );
+  auto slice = dds.get_slice( 100 );
+  REQUIRE( slice );
+
+  using LHCb::Detector::DeVP;
+  DeVP de = slice->get( det, LHCb::Detector::Keys::deKey );
+  REQUIRE( !!de );
+
+  using dd4hep::deg;
+  using dd4hep::mm;
+  using dd4hep::Position;
+  using dd4hep::RotationZ;
+  using dd4hep::RotationZYX;
+  using dd4hep::Translation3D;
+
+  // check that we have what we expect
+  auto align = getDelta( de );
+  CHECK( align.hasTranslation() );
+  CHECK( !align.hasRotation() );
+  CHECK( !align.hasPivot() );
+  CHECK_THAT( align.translation.X(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Y(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Z(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+
+  // phase 3: change the alignment parameters and update the overlay
+  {
+    RotationZYX   rot{RotationZ{5 * deg}};
+    dd4hep::Delta new_align{Position{1 * mm, 4 * mm, 2.5 * mm}, rot};
+    dds.update_alignment( de.detector(), new_align );
+    // We cannot check anything here because the overlay instance is private to the system
+  }
+
+  // phase 4: reload alignments (from overlay)
+  dds.clear_slice_cache(); // this is to make sure we force a reload of everything
+  auto slice2 = dds.get_slice( 100 );
+
+  REQUIRE( slice2 );
+  REQUIRE( slice != slice2 );
+  de = slice2->get( det, LHCb::Detector::Keys::deKey );
+  CHECK( !!de );
+
+  // check that the change in memory as no effect on the alignment that should not
+  // come from the overlay
+  align = getDelta( de );
+  CHECK( align.hasTranslation() );
+  CHECK( !align.hasRotation() );
+  CHECK( !align.hasPivot() );
+  CHECK_THAT( align.translation.X(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Y(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Z(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+
+  // phase 5: change again in memory and dump alignments to files
+  {
+    RotationZYX   rot{RotationZ{5 * deg}};
+    dd4hep::Delta new_align{Position{1 * mm, 4 * mm, 2.5 * mm}, rot};
+    dds.update_alignment( de.detector(), new_align );
+  }
+  dds.dump_conditions( tmp.path );
+  {
+    REQUIRE( fs::exists( cond_dir / "Global.yml" ) );
+    // dump_conditions (by default) should not write unchanged entries
+    CHECK( !fs::exists( cond_dir / "Ladders.yml" ) );
+
+    // check that the YAML file contains the expected changes
+    auto gbl = YAML::LoadFile( tmp.path / "Conditions" / "VP" / "Alignment" / "Global.yml" );
+    auto sys = gbl["VPSystem"];
+    REQUIRE( sys );
+    CHECK( sys.Tag() == "!alignment" );
+    std::stringstream ss;
+    ss << sys["position"];
+    CHECK( ss.str() == "[1 * mm, 4 * mm, 2.5 * mm]" );
+    CHECK( sys["rotation"] );
+    CHECK( !sys["pivot"] );
+  }
+
+  // phase 6: reload the overlay from files
+  dds.load_conditions( tmp.path );
+
+  // phase 7: reload alignments (from overlay)
+  dds.clear_slice_cache();
+  auto slice3 = dds.get_slice( 100 );
+
+  REQUIRE( slice3 );
+  REQUIRE( slice2 != slice3 );
+  de = slice3->get( det, LHCb::Detector::Keys::deKey );
+  CHECK( !!de );
+
+  // we still expect the alignment to be the one we have in the DB (and not the overlay)
+  align = getDelta( de );
+  CHECK( align.hasTranslation() );
+  CHECK( !align.hasRotation() );
+  CHECK( !align.hasPivot() );
+  CHECK_THAT( align.translation.X(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Y(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+  CHECK_THAT( align.translation.Z(), Catch::Matchers::WithinAbs( 0 * mm, 1.e-6 ) );
+}