Repository: 7mind/izumi
Pull request #1146 (2020-07-14 16:38:22)

Feature/flexible reuse
@Caparow : could you update `ContainerNetworkDef` to be aligned with the new reuse policy approach?

Caparow: > @Caparow : could you update `ContainerNetworkDef` to be aligned with the new reuse policy approach?

Yeah, I've updated `ContainerNetworkDef`.

Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
index 89679ef0d..5f689aa09 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/DebugProperties.scala
@@ -8,6 +8,6 @@ import izumi.fundamentals.platform.properties
   * @see [[DebugProperties]]
   */
 object DebugProperties extends properties.DebugProperties {
-  final val `izumi.debug.macro.distage.constructors` = Property("izumi.debug.macro.distage.constructors")
-  final val `izumi.debug.macro.distage.providermagnet` = Property("izumi.debug.macro.distage.providermagnet")
+  final val `izumi.debug.macro.distage.constructors` = BoolProperty("izumi.debug.macro.distage.constructors")
+  final val `izumi.debug.macro.distage.providermagnet` = BoolProperty("izumi.debug.macro.distage.providermagnet")
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
index ab38da66d..cb6b92bf7 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/AnyConstructorMacro.scala
@@ -21,7 +21,7 @@ object AnyConstructorMacro {
   def anyConstructorOptionalMakeDSL[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AnyConstructorOptionalMakeDSL.Impl[T]] = {
     import c.universe._
 
-    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
+    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name)
 
     val enclosingClass = c.enclosingClass
     // We expect this macro to be called only and __ONLY__ from `AnyConstructorMacro.make`
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
index 0b203f1eb..1a1d1d255 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/ClassConstructorMacro.scala
@@ -42,7 +42,7 @@ object ClassConstructorMacro {
           )
         }
 
-        val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
+        val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name)
 
         val provider: c.Expr[ProviderMagnet[T]] = mkClassConstructorProvider(reflectionProvider)(targetType)
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
index bf8b6938d..9dec9f91c 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/FactoryConstructorMacro.scala
@@ -16,7 +16,7 @@ object FactoryConstructorMacro {
 
     val macroUniverse = StaticDIUniverse(c)
     val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse)
-    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
+    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name)
 
     val targetType = ReflectionUtil.norm(c.universe: c.universe.type)(weakTypeOf[T].dealias)
     requireConcreteTypeConstructor(c)("FactoryConstructor", targetType)
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/HasConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/HasConstructorMacro.scala
index 647ae538a..9c0fe111c 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/HasConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/HasConstructorMacro.scala
@@ -31,7 +31,7 @@ object HasConstructorMacro {
         ziohasConstructorAssertion(targetType, deepIntersection)
 
         val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse)
-        val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
+        val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name)
 
         val params = reflectionProvider.zioHasParameters(c.freshName)(deepIntersection)
         val provider: c.Expr[ProviderMagnet[T]] = {
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
index d030f3ef1..677b2c9ee 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/constructors/macros/TraitConstructorMacro.scala
@@ -21,7 +21,7 @@ object TraitConstructorMacro {
     traitConstructorAssertion(targetType)
 
     val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse)
-    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`)
+    val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.constructors`.name)
 
     val provider: c.Expr[ProviderMagnet[T]] = mkTraitConstructorProvider(symbolToTrait(reflectionProvider)(targetType))
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
index 0d1933a70..cb98d3fdf 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/reflection/macros/ProviderMagnetMacro.scala
@@ -24,7 +24,7 @@ class ProviderMagnetMacro(val c: blackbox.Context) {
 
   final val macroUniverse: Aux[c.universe.type] = StaticDIUniverse(c)
 
-  private final val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.providermagnet`)
+  private final val logger = TrivialMacroLogger.make[this.type](c, DebugProperties.`izumi.debug.macro.distage.providermagnet`.name)
   private final val reflectionProvider = ReflectionProviderDefaultImpl(macroUniverse)
 
   import c.universe._
diff --git a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
index 34ee0fc32..01ba48a0b 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
@@ -79,7 +79,7 @@ object BootstrapLocator {
 
     val bootstrapObserver = new PlanningObserverAggregate(
       Set(
-        new BootstrapPlanningObserver(TrivialLogger.make[BootstrapLocator](DebugProperties.`izumi.distage.debug.bootstrap`))
+        new BootstrapPlanningObserver(TrivialLogger.make[BootstrapLocator](DebugProperties.`izumi.distage.debug.bootstrap`.name))
         //new GraphObserver(analyzer, Set.empty),
       )
     )
diff --git a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/DebugProperties.scala b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/DebugProperties.scala
index cea4fcc82..37eff0f05 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/DebugProperties.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/DebugProperties.scala
@@ -4,5 +4,5 @@ import izumi.fundamentals.platform.properties
 
 object DebugProperties extends properties.DebugProperties {
   /** Print debug messages when planning Injector's own bootstrap environment */
-  final val `izumi.distage.debug.bootstrap` = Property("izumi.distage.debug.bootstrap")
+  final val `izumi.distage.debug.bootstrap` = BoolProperty("izumi.distage.debug.bootstrap")
 }
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
index 9656fc724..8a9da135b 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/DebugProperties.scala
@@ -9,5 +9,5 @@ import izumi.fundamentals.platform.properties
   */
 object DebugProperties extends properties.DebugProperties {
   /** Scan classpath only once per test run */
-  final val `izumi.distage.plugins.cache` = Property("izumi.distage.plugins.cache")
+  final val `izumi.distage.plugins.cache` = BoolProperty("izumi.distage.plugins.cache")
 }
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
index b7dad0d99..146c59b6f 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
@@ -34,5 +34,5 @@ object PluginConfig {
   def const(plugin: PluginBase): PluginConfig = const(Seq(plugin))
   lazy val empty: PluginConfig = const(Nil)
 
-  private[this] lazy val cacheEnabled: Boolean = DebugProperties.`izumi.distage.plugins.cache`.asBoolean(true)
+  private[this] lazy val cacheEnabled: Boolean = DebugProperties.`izumi.distage.plugins.cache`.boolValue(true)
 }
diff --git a/distage/distage-framework-docker/src/main/resources/docker-reference.conf b/distage/distage-framework-docker/src/main/resources/docker-reference.conf
index 9ef40d1e1..873b5e0c5 100644
--- a/distage/distage-framework-docker/src/main/resources/docker-reference.conf
+++ b/distage/distage-framework-docker/src/main/resources/docker-reference.conf
@@ -1,7 +1,7 @@
 docker {
   readTimeoutMs = 60000
   connectTimeoutMs = 1000
-  allowReuse = true
+  globalReuse = "ReuseEnabled"
 
   useRemote = false
   useRegistry = false
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerNetworkDef.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerNetworkDef.scala
index aa518dab9..2edfcb2f5 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerNetworkDef.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerNetworkDef.scala
@@ -3,18 +3,19 @@ package izumi.distage.docker
 import java.util.UUID
 
 import izumi.distage.docker.ContainerNetworkDef.{ContainerNetwork, ContainerNetworkConfig}
+import izumi.distage.docker.Docker.DockerReusePolicy
 import izumi.distage.framework.model.exceptions.IntegrationCheckException
 import izumi.distage.model.definition.DIResource
 import izumi.distage.model.effect.{DIEffect, DIEffectAsync}
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.fundamentals.platform.integration.ResourceCheck
+import izumi.fundamentals.platform.language.Quirks._
 import izumi.fundamentals.platform.strings.IzString._
-import izumi.reflect.TagK
 import izumi.logstage.api.IzLogger
-import izumi.fundamentals.platform.language.Quirks._
+import izumi.reflect.TagK
 
-import scala.jdk.CollectionConverters._
 import scala.concurrent.duration._
+import scala.jdk.CollectionConverters._
 
 trait ContainerNetworkDef {
   type Tag
@@ -44,21 +45,17 @@ object ContainerNetworkDef {
   ) extends DIResource[F, ContainerNetwork[T]] {
     private[this] val rawClient = client.rawClient
     private[this] val prefix: String = prefixName.camelToUnderscores.drop(1).replace("$", "")
-    private[this] val stableLabels: Map[String, String] = Map(
-      DockerConst.Labels.reuseLabel -> shouldReuse(config),
+    private[this] val networkLabels: Map[String, String] = Map(
+      DockerConst.Labels.reuseLabel -> Docker.shouldReuse(config.reuse, client.clientConfig.globalReuse),
       s"${DockerConst.Labels.networkDriverPrefix}.${config.driver}" -> true.toString,
       DockerConst.Labels.namePrefixLabel -> prefix,
     ).map { case (k, v) => k -> v.toString }
 
-    private[this] def shouldReuse(config: ContainerNetworkConfig[T]): Boolean = {
-      config.reuse && client.clientConfig.allowReuse
-    }
-
     override def acquire: F[ContainerNetwork[T]] = {
       integrationCheckHack {
-        if (config.reuse) {
+        if (Docker.shouldReuse(config.reuse, client.clientConfig.globalReuse)) {
           FileLockMutex.withLocalMutex(logger)(prefix, waitFor = 1.second, maxAttempts = 10) {
-            val labelsSet = stableLabels.toSet
+            val labelsSet = networkLabels.toSet
             val existedNetworks = rawClient.listNetworksCmd().exec().asScala.toList
             existedNetworks.find(_.labels.asScala.toSet == labelsSet).fold(createNew()) {
               network =>
@@ -72,7 +69,7 @@ object ContainerNetworkDef {
     }
 
     override def release(resource: ContainerNetwork[T]): F[Unit] = {
-      if (shouldReuse(config)) {
+      if (Docker.shouldKill(config.reuse, client.clientConfig.globalReuse)) {
         DIEffect[F].unit
       } else {
         DIEffect[F].maybeSuspend {
@@ -90,7 +87,7 @@ object ContainerNetworkDef {
         .createNetworkCmd()
         .withName(name)
         .withDriver(config.driver)
-        .withLabels(stableLabels.asJava)
+        .withLabels(networkLabels.asJava)
         .exec()
       ContainerNetwork(name, network.getId)
     }
@@ -115,6 +112,6 @@ object ContainerNetworkDef {
   final case class ContainerNetworkConfig[Tag](
     name: Option[String] = None,
     driver: String = "bridge",
-    reuse: Boolean = true,
+    reuse: DockerReusePolicy = DockerReusePolicy.KeepAliveOnExitAndReuse,
   )
 }
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerResource.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerResource.scala
index 0d4f47258..ce617925c 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerResource.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/ContainerResource.scala
@@ -19,6 +19,7 @@ import izumi.fundamentals.platform.integration.ResourceCheck
 import izumi.fundamentals.platform.network.IzSockets
 import izumi.fundamentals.platform.strings.IzString._
 import izumi.logstage.api.IzLogger
+import izumi.fundamentals.platform.basics.IzBoolean._
 
 import scala.annotation.nowarn
 import scala.concurrent.duration._
@@ -44,10 +45,6 @@ case class ContainerResource[F[_], T](
     }
   }
 
-  private[this] def shouldReuse(config: Docker.ContainerConfig[T]): Boolean = {
-    config.reuse == DockerReusePolicy.KeepAliveOnExitAndReuse && client.clientConfig.allowReuse
-  }
-
   override def acquire: F[DockerContainer[T]] = F.suspendF {
     val ports = config.ports.map {
       containerPort =>
@@ -64,18 +61,18 @@ case class ContainerResource[F[_], T](
         PortDecl(containerPort, local, binding, labels)
     }
 
-    if (shouldReuse(config)) {
+    if (Docker.shouldReuse(config.reuse, client.clientConfig.globalReuse)) {
       runReused(ports)
     } else {
       doRun(ports)
     }
   }
 
-  override def release(resource: DockerContainer[T]): F[Unit] = {
-    if (!shouldReuse(resource.containerConfig)) {
-      client.destroyContainer(resource.id, ContainerDestroyMeta.ParameterizedContainer(resource))
+  override def release(container: DockerContainer[T]): F[Unit] = {
+    if (Docker.shouldKill(container.containerConfig.reuse, container.clientConfig.globalReuse)) {
+      client.destroyContainer(container.id, ContainerDestroyMeta.ParameterizedContainer(container))
     } else {
-      F.unit
+      F.maybeSuspend(logger.info(s"Will not destroy: $container"))
     }
   }
 
@@ -371,7 +368,7 @@ case class ContainerResource[F[_], T](
   }
 
   private val stableLabels = Map(
-      DockerConst.Labels.reuseLabel -> shouldReuse(config).toString
+      DockerConst.Labels.reuseLabel -> Docker.shouldReuse(config.reuse, client.clientConfig.globalReuse).toString
     ) ++ client.labels
 }
 
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/Docker.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/Docker.scala
index 07b77a385..156379534 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/Docker.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/Docker.scala
@@ -6,6 +6,8 @@ import izumi.distage.config.codec.DIConfigReader
 import izumi.distage.docker.ContainerNetworkDef.ContainerNetwork
 import izumi.distage.docker.healthcheck.ContainerHealthCheck
 import izumi.fundamentals.collections.nonempty.NonEmptyList
+import izumi.fundamentals.platform.basics.IzBoolean.any
+import pureconfig.ConfigReader
 
 import scala.concurrent.duration.FiniteDuration
 
@@ -52,8 +54,56 @@ object Docker {
 
   sealed trait DockerReusePolicy
   object DockerReusePolicy {
-    case object KillOnExit extends DockerReusePolicy
+    case object KillOnExitNoReuse extends DockerReusePolicy
     case object KeepAliveOnExitAndReuse extends DockerReusePolicy
+    case object KillOnExitButReuse extends DockerReusePolicy
+  }
+
+  sealed trait GlobalDockerReusePolicy {
+    def enabled: Boolean
+  }
+  object GlobalDockerReusePolicy {
+    case object ReuseDisabled extends GlobalDockerReusePolicy {
+      override def enabled: Boolean = false
+    }
+    case object ReuseButAlwaysKill extends GlobalDockerReusePolicy {
+      override def enabled: Boolean = true
+    }
+    case object ReuseEnabled extends GlobalDockerReusePolicy {
+      override def enabled: Boolean = true
+    }
+  }
+
+  private[docker] def shouldReuse(reusePolicy: DockerReusePolicy, globalReuse: GlobalDockerReusePolicy): Boolean = {
+    val reuseIsOnForContainer = any(
+      reusePolicy == DockerReusePolicy.KeepAliveOnExitAndReuse,
+      reusePolicy == DockerReusePolicy.KillOnExitButReuse,
+    )
+
+    globalReuse match {
+      case GlobalDockerReusePolicy.ReuseDisabled =>
+        false
+      case GlobalDockerReusePolicy.ReuseButAlwaysKill =>
+        reuseIsOnForContainer
+      case GlobalDockerReusePolicy.ReuseEnabled =>
+        reuseIsOnForContainer
+    }
+  }
+
+  private[docker] def shouldKill(reusePolicy: DockerReusePolicy, globalReuse: GlobalDockerReusePolicy): Boolean = {
+    val containerIsVictim = any(
+      reusePolicy == DockerReusePolicy.KillOnExitNoReuse,
+      reusePolicy == DockerReusePolicy.KillOnExitButReuse,
+    )
+
+    globalReuse match {
+      case GlobalDockerReusePolicy.ReuseDisabled =>
+        true
+      case GlobalDockerReusePolicy.ReuseButAlwaysKill =>
+        true
+      case GlobalDockerReusePolicy.ReuseEnabled =>
+        containerIsVictim
+    }
   }
 
   /**
@@ -105,7 +155,7 @@ object Docker {
     user: Option[String] = None,
     mounts: Seq[Mount] = Seq.empty,
     networks: Set[ContainerNetwork[_]] = Set.empty,
-    reuse: DockerReusePolicy = DockerReusePolicy.KillOnExit,
+    reuse: DockerReusePolicy = DockerReusePolicy.KillOnExitNoReuse,
     autoRemove: Boolean = true,
     healthCheckInterval: FiniteDuration = FiniteDuration(1, TimeUnit.SECONDS),
     healthCheckMaxAttempts: Int = 120,
@@ -122,7 +172,7 @@ object Docker {
     * See `docker-reference.conf` for an example configuration.
     * You can `include` the reference configuration if you want to use defaults.
     *
-    * @param allowReuse   If true and container's [[ContainerConfig#reuse]] is also true, keeps container alive after
+    * @param globalReuse   If true and container's [[ContainerConfig#reuse]] is also true, keeps container alive after
     *                     initialization. If false, the container will be shut down.
     *
     * @param remote       Options to connect to a Remote Docker Daemon,
@@ -142,14 +192,34 @@ object Docker {
   final case class ClientConfig(
     readTimeoutMs: Int = 60000,
     connectTimeoutMs: Int = 1000,
-    allowReuse: Boolean = !DockerSupportProperties.`izumi.distage.docker.reuse-disable`.asBoolean(false),
+    globalReuse: GlobalDockerReusePolicy = ClientConfig.parseReusePolicy(),
     useRemote: Boolean = false,
     useRegistry: Boolean = false,
     remote: Option[RemoteDockerConfig] = None,
     registry: Option[DockerRegistryConfig] = None,
   )
+
   object ClientConfig {
+    implicit val policyReader: ConfigReader[GlobalDockerReusePolicy] = ConfigReader[String].map(name => parseReusePolicy(Some(name)))
+
     implicit val distageConfigReader: DIConfigReader[ClientConfig] = DIConfigReader.derived
+
+    def parseReusePolicy(): GlobalDockerReusePolicy = {
+      parseReusePolicy(DockerSupportProperties.`izumi.distage.docker.reuse`.strValue())
+    }
+
+    def parseReusePolicy(name: Option[String]): GlobalDockerReusePolicy = {
+      name match {
+        case Some("ReuseDisabled") =>
+          GlobalDockerReusePolicy.ReuseDisabled
+        case Some("ReuseButAlwaysKill") =>
+          GlobalDockerReusePolicy.ReuseButAlwaysKill
+        case Some("ReuseButAlwaysKill") =>
+          GlobalDockerReusePolicy.ReuseEnabled
+        case other =>
+          throw new IllegalArgumentException(s"Unexpected config value for reuse policy: $other")
+      }
+    }
   }
 
   final case class RemoteDockerConfig(host: String, tlsVerify: Boolean, certPath: String, config: String)
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerSupportProperties.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerSupportProperties.scala
index e768337ef..cc93de98f 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerSupportProperties.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/DockerSupportProperties.scala
@@ -8,5 +8,5 @@ import izumi.fundamentals.platform.properties
   * @see [[properties.DebugProperties]]
   */
 object DockerSupportProperties extends properties.DebugProperties {
-  final val `izumi.distage.docker.reuse-disable` = Property("izumi.distage.docker.reuse-disable")
+  final val `izumi.distage.docker.reuse` = StrProperty("izumi.distage.docker.reuse")
 }
diff --git a/doc/microsite/src/main/tut/distage/distage-framework-docker.md b/doc/microsite/src/main/tut/distage/distage-framework-docker.md
index 2f2071a0a..f241ac6fb 100644
--- a/doc/microsite/src/main/tut/distage/distage-framework-docker.md
+++ b/doc/microsite/src/main/tut/distage/distage-framework-docker.md
@@ -455,11 +455,11 @@ container.
 
 Container networks, like containers, are reused by default. If there is an existing network that
 matches a definition then that network will be used. This can be disabled by setting the `reuse`
-configuration to false:
+configuration to `DockerReusePolicy.KillOnExitNoReuse`:
 
 ```mdoc:silent
 object AlwaysFreshNetwork extends ContainerNetworkDef {
-  override def config: Config = Config(reuse = false)
+  override def config: Config = Config(reuse = Docker.DockerReusePolicy.KillOnExitNoReuse)
 }
 ```
 
@@ -483,7 +483,7 @@ include "docker-reference.conf"
 # override docker object fields
 docker {
   readTimeoutMs = 60000
-  allowReuse = false
+  globalReuse = "always-kill"
 }
 ```
 
@@ -491,6 +491,7 @@ docker {
 
 ```scala mdoc:to-string
 import izumi.distage.docker.Docker
+import izumi.distage.docker.Docker.GlobalDockerReusePolicy
 
 class CustomDockerConfigExampleModule[F[_]: TagK] extends ModuleDef {
   include(DockerSupportModule[F] overridenBy new ModuleDef {
@@ -498,7 +499,7 @@ class CustomDockerConfigExampleModule[F[_]: TagK] extends ModuleDef {
       Docker.ClientConfig(
         readTimeoutMs    = 10000,
         connectTimeoutMs = 10000,
-        allowReuse       = false,
+        globalReuse      = GlobalDockerReusePolicy.ReuseButAlwaysKill,
         useRemote        = true,
         useRegistry      = true,
         remote           = Some(
@@ -536,16 +537,31 @@ matching container is found this container is referenced by the
 
 For an existing container to be reused, all the following must be true:
 
-- The current client config has `allowReuse == true`.
-- The container config has `reuse == true`.
+- The current client config has `globalReuse == GlobalDockerReusePolicy.ReuseEnabled` or `globalReuse == GlobalDockerReusePolicy.ReuseButAlwaysKill`.
+- The container config has `reuse == DockerReusePolicy.KeepAliveOnExitAndReuse` or `reuse == DockerReusePolicy.KillOnExitButReuse`.
 - The running container was created for reuse.
 - The running container uses the same image as the container config.
 - All ports requested in the container config must be mapped for the running container.
 
 #### Configuring Reuse
 
-The `ContainerDef.Config.reuse` should be false to disable reuse for a specific container.  While
-`Docker.ClientConfig.allowReuse` should be false to disable reuse throughout the application.
+`DockerReusePolicy` has 3 possible values:
+
+- `KillOnExitNoReuse`: the resource (container or network) will be always freed after use and existing matching containers will not be reused
+- `KeepAliveOnExitAndReuse`: the resource will not be freed after use and existing matching containers will be reused
+- `KillOnExitButReuse`: the resource will be always be freed after use but existing matching containers will be reused
+
+
+`GlobalDockerReusePolicy` allows you to override defaults:
+
+- `ReuseDisabled`: no resources will be reused, `DockerReusePolicy` will not be respected
+- `ReuseButAlwaysKill`: existing matching resources will be reused but they will always be freed after use (`DockerReusePolicy.KeepAliveOnExitAndReuse` will act as `DockerReusePolicy.KillOnExitButReuse`)
+- `ReuseEnabled`: `DockerReusePolicy` will be respected
+
+
+The `ContainerDef.Config.reuse` should be `DockerReusePolicy.KillOnExitNoReuse` to disable reuse for a specific container.  While
+`Docker.ClientConfig.globalReuse` should be `GlobalDockerReusePolicy.ReuseDisabled` to disable reuse throughout the application.
+
 
 #### Improving Reuse Performance
 
diff --git a/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/properties/DebugProperties.scala b/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/properties/DebugProperties.scala
index 1882e626e..e1d3b7458 100644
--- a/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/properties/DebugProperties.scala
+++ b/fundamentals/fundamentals-platform/src/main/scala/izumi/fundamentals/platform/properties/DebugProperties.scala
@@ -17,14 +17,23 @@ import scala.language.implicitConversions
   * }}}
   */
 trait DebugProperties {
-  @inline protected[this] final def Property(name: String): BooleanProperty = new BooleanProperty(name)
+  @inline protected[this] final def BoolProperty(name: String): BooleanProperty = new BooleanProperty(name)
+
+  @inline protected[this] final def StrProperty(name: String): StrProperty = new StrProperty(name)
 }
 
 final case class BooleanProperty(name: String) extends AnyVal {
-  def asBoolean(default: Boolean): Boolean = {
+  def boolValue(default: Boolean): Boolean = {
     System.getProperty(name).asBoolean().getOrElse(default)
   }
 }
-object BooleanProperty {
-  @inline implicit def asString(property: BooleanProperty): String = property.name
+
+final case class StrProperty(name: String) extends AnyVal {
+  def strValue(default: String): String = {
+    Option(System.getProperty(name)).getOrElse(default)
+  }
+
+  def strValue(): Option[String] = {
+    Option(System.getProperty(name))
+  }
 }
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
index 751718427..38a9c06e5 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
@@ -3,7 +3,7 @@ package izumi.logstage
 import izumi.fundamentals.platform.properties
 
 object DebugProperties extends properties.DebugProperties {
-  final val `izumi.debug.macro.logstage` = Property("izumi.debug.macro.logstage")
-  final val `izumi.logstage.rendering.colored` = Property("izumi.logstage.rendering.colored")
-  final val `izumi.logstage.rendering.colored.forced` = Property("izumi.logstage.rendering.colored.forced")
+  final val `izumi.debug.macro.logstage` = BoolProperty("izumi.debug.macro.logstage")
+  final val `izumi.logstage.rendering.colored` = BoolProperty("izumi.logstage.rendering.colored")
+  final val `izumi.logstage.rendering.colored.forced` = BoolProperty("izumi.logstage.rendering.colored.forced")
 }
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/StringRenderingPolicy.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/StringRenderingPolicy.scala
index 6765b5673..4fd3dc366 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/StringRenderingPolicy.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/StringRenderingPolicy.scala
@@ -19,10 +19,10 @@ class StringRenderingPolicy(
     val colorsEnabled = any(
       all(
         options.colored,
-        DebugProperties.`izumi.logstage.rendering.colored`.asBoolean(true),
+        DebugProperties.`izumi.logstage.rendering.colored`.boolValue(true),
         IzJvm.terminalColorsEnabled,
       ),
-      DebugProperties.`izumi.logstage.rendering.colored.forced`.asBoolean(false),
+      DebugProperties.`izumi.logstage.rendering.colored.forced`.boolValue(false),
     )
 
     options.copy(colored = colorsEnabled)
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
index 4932aa267..32e185458 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
@@ -10,7 +10,7 @@ import scala.reflect.macros.blackbox
 import scala.util.{Failure, Success}
 
 class ArgumentNameExtractionMacro[C <: blackbox.Context](final val c: C, strict: Boolean) {
-  private[this] final val applyDebug = DebugProperties.`izumi.debug.macro.logstage`.asBoolean(false)
+  private[this] final val applyDebug = DebugProperties.`izumi.debug.macro.logstage`.boolValue(false)
 
   import ExtractedName._
   import c.universe._


Test patch:
diff --git a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/ReusedOneshotContainer.scala b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/ReusedOneshotContainer.scala
index e3bc34aad..442d982b7 100644
--- a/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/ReusedOneshotContainer.scala
+++ b/distage/distage-framework-docker/src/test/scala/izumi/distage/testkit/docker/fixtures/ReusedOneshotContainer.scala
@@ -26,7 +26,7 @@ object ReuseCheckContainer extends ContainerDef {
       ports = Seq(),
       mounts = Seq(CmdContainerModule.stateFileMount),
       entrypoint = Seq("sh", "-c", s"if [[ $$(cat ${CmdContainerModule.stateFilePath} | wc -l | awk '{print $$1}') == 1 ]]; then exit 0; else exit 42; fi"),
-      reuse = DockerReusePolicy.KillOnExit,
+      reuse = DockerReusePolicy.KillOnExitNoReuse,
     )
   }
 }
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
index 753a11b7c..8d9e2d8a8 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/DebugProperties.scala
@@ -9,8 +9,8 @@ import izumi.fundamentals.platform.properties
   */
 object DebugProperties extends properties.DebugProperties {
   /** Print debug messages, `false` by default */
-  final val `izumi.distage.testkit.debug` = Property("izumi.distage.testkit.debug")
+  final val `izumi.distage.testkit.debug` = BoolProperty("izumi.distage.testkit.debug")
 
   /** Cache created [[izumi.distage.testkit.services.dstest.TestEnvironment]], `true` by default */
-  final val `izumi.distage.testkit.environment.cache` = Property("izumi.distage.testkit.environment.cache")
+  final val `izumi.distage.testkit.environment.cache` = BoolProperty("izumi.distage.testkit.environment.cache")
 }
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala
index 0c22543d6..a11ac5d4f 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestEnv.scala
@@ -19,7 +19,7 @@ trait DistageTestEnv {
       makeEnv(testConfig, pluginLoader, roles, mergeStrategy)
     }
 
-    if (DebugProperties.`izumi.distage.testkit.environment.cache`.asBoolean(true)) {
+    if (DebugProperties.`izumi.distage.testkit.environment.cache`.boolValue(true)) {
       DistageTestEnv.cache.getOrCompute(DistageTestEnv.EnvCacheKey(testConfig, roles, mergeStrategy), doMake())
     } else {
       doMake()
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
index aaf5a63b5..416518f01 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/DistageTestRunner.scala
@@ -578,7 +578,7 @@ object DistageTestRunner {
     }
   }
 
-  private val enableDebugOutput = DebugProperties.`izumi.distage.testkit.debug`.asBoolean(false)
+  private val enableDebugOutput = DebugProperties.`izumi.distage.testkit.debug`.boolValue(false)
 
   private val memoizedConfig = new ConcurrentHashMap[(String, BootstrapFactory, Option[AppConfig]), AppConfig]()
 
diff --git a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
index 0e874063a..db564deb6 100644
--- a/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
+++ b/distage/distage-testkit-scalatest/src/main/scala/org/scalatest/distage/DistageScalatestTestSuiteRunner.scala
@@ -50,7 +50,7 @@ object ScalatestInitWorkaround {
         try {
           val suiteClassName = classOf[DistageScalatestTestSuiteRunner[Identity]].getName
           val testClasses = scan.getSubclasses(suiteClassName).asScala.filterNot(_.isAbstract)
-          lazy val debugLogger = TrivialLogger.make[ScalatestInitWorkaroundImpl.type](DebugProperties.`izumi.distage.testkit.debug`)
+          lazy val debugLogger = TrivialLogger.make[ScalatestInitWorkaroundImpl.type](DebugProperties.`izumi.distage.testkit.debug`.name)
           testClasses.foreach(
             classInfo =>
               Try {
@@ -159,7 +159,7 @@ abstract class DistageScalatestTestSuiteRunner[F[_]](implicit override val tagMo
   }
 
   private[distage] def doRun(testsInThisRuntime: Seq[DistageTest[F]], testName: Option[String], args: Args): Unit = {
-    val debugLogger: TrivialLogger = TrivialLogger.make[DistageScalatestTestSuiteRunner[F]](DebugProperties.`izumi.distage.testkit.debug`)
+    val debugLogger: TrivialLogger = TrivialLogger.make[DistageScalatestTestSuiteRunner[F]](DebugProperties.`izumi.distage.testkit.debug`.name)
     debugLogger.log(s"Scalatest Args: $args")
     debugLogger.log(s"""tagsToInclude: ${args.filter.tagsToInclude}
                        |tagsToExclude: ${args.filter.tagsToExclude}