Repository: containers/podman
Pull request #25676: Fix overwriting the Healthcheck configuration from the image
Author: login: Honny1
Labels: name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: release-note
name: locked - please file new issue/PR
description: Assist humans wanting to comment on an old issue or PR with locked comments.
Created: 2025-03-25T13:20:55Z | Merged: 2025-04-03T15:31:28Z

This PR fixes overwriting the Healthcheck configuration from the image. If the `--health-cmd` flag is not specified, other flags such as `--health-interval`, `--health-timeout`, `--health-retries`, and `--health-start-period` are ignored if the image contains a Healthcheck. This makes it impossible to modify these Healthcheck configurations when a container is created.

Fixes: https://github.com/containers/podman/issues/20212
Fixes: https://issues.redhat.com/browse/RUN-2629

<!--
Thanks for sending a pull request!

Please make sure you've read our contributing guidelines and how to submit a pull request (https://github.com/containers/podman/blob/main/CONTRIBUTING.md#submitting-pull-requests).

Finally, be sure to sign commits with your real name. Since by opening
a PR you already have commits, you can add signatures if needed with
something like `git commit -s --amend`.
-->

#### Does this PR introduce a user-facing change?

<!--
If no, just write `None` in the release-note block below. If yes, a release note
is required: Enter your extended release note in the block below. If the PR
requires additional action from users switching to the new release, include the
string "action required".

For more information on release notes, please follow the Kubernetes model:
https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
Users can now override HealthCheck information provided via container images.
```



Diff:
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index ff0865b821b..d1d083beeab 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -1,9 +1,13 @@
 package common
 
 import (
+	"errors"
+	"time"
+
 	"github.com/containers/common/pkg/auth"
 	"github.com/containers/common/pkg/completion"
 	commonFlag "github.com/containers/common/pkg/flag"
+	"github.com/containers/image/v5/manifest"
 	"github.com/containers/podman/v5/cmd/podman/registry"
 	"github.com/containers/podman/v5/libpod/define"
 	"github.com/containers/podman/v5/pkg/domain/entities"
@@ -1053,3 +1057,43 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
+	healthcheck := manifest.Schema2HealthConfig{}
+
+	if cmd.Flags().Changed("health-interval") {
+		if vals.HealthInterval == "disable" {
+			vals.HealthInterval = "-1s"
+		}
+		hct, err := time.ParseDuration(vals.HealthInterval)
+		if err != nil {
+			return nil, err
+		}
+		healthcheck.Interval = hct
+	}
+	if cmd.Flags().Changed("health-retries") {
+		healthcheck.Retries = int(vals.HealthRetries)
+	}
+	if cmd.Flags().Changed("health-timeout") {
+		hct, err := time.ParseDuration(vals.HealthTimeout)
+		if err != nil {
+			return nil, err
+		}
+		if hct < time.Duration(1) {
+			return nil, errors.New("healthcheck-timeout must be at least 1 second")
+		}
+		healthcheck.Timeout = hct
+	}
+	if cmd.Flags().Changed("health-start-period") {
+		hct, err := time.ParseDuration(vals.HealthStartPeriod)
+		if err != nil {
+			return nil, err
+		}
+		if hct < time.Duration(0) {
+			return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
+		}
+		healthcheck.StartPeriod = hct
+	}
+
+	return &healthcheck, nil
+}
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index dd3411bef5d..617da4a8255 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -174,6 +174,13 @@ func create(cmd *cobra.Command, args []string) error {
 		}
 	}
 
+	if s.HealthConfig == nil {
+		s.HealthConfig, err = common.GetHealthCheckOverrideConfig(cmd, &cliVals)
+		if err != nil {
+			return err
+		}
+	}
+
 	report, err := registry.ContainerEngine().ContainerCreate(registry.Context(), s)
 	if err != nil {
 		// if pod was created as part of run
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index 7b95914cdef..665e0082a0e 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -225,6 +225,13 @@ func run(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
+	if s.HealthConfig == nil {
+		s.HealthConfig, err = common.GetHealthCheckOverrideConfig(cmd, &cliVals)
+		if err != nil {
+			return err
+		}
+	}
+
 	report, err := registry.ContainerEngine().ContainerRun(registry.Context(), runOpts)
 	// report.ExitCode is set by ContainerRun even it returns an error
 	if report != nil {
diff --git a/docs/source/markdown/options/health-cmd.md b/docs/source/markdown/options/health-cmd.md
index 4e094308492..cc1587796d9 100644
--- a/docs/source/markdown/options/health-cmd.md
+++ b/docs/source/markdown/options/health-cmd.md
@@ -10,3 +10,5 @@ to be applied. A value of **none** disables existing healthchecks.
 
 Multiple options can be passed in the form of a JSON array; otherwise, the command is interpreted
 as an argument to **/bin/sh -c**.
+
+Note: The default values are used even if healthcheck is configured in the image.
diff --git a/docs/source/markdown/options/health-interval.md b/docs/source/markdown/options/health-interval.md
index 0b25517cfaf..2657187e592 100644
--- a/docs/source/markdown/options/health-interval.md
+++ b/docs/source/markdown/options/health-interval.md
@@ -5,3 +5,5 @@
 #### **--health-interval**=*interval*
 
 Set an interval for the healthchecks. An _interval_ of **disable** results in no automatic timer setup. The default is **30s**.
+
+Note: This parameter will overwrite related healthcheck configuration from the image.
diff --git a/docs/source/markdown/options/health-retries.md b/docs/source/markdown/options/health-retries.md
index 13d68afb956..4060fb30ed0 100644
--- a/docs/source/markdown/options/health-retries.md
+++ b/docs/source/markdown/options/health-retries.md
@@ -5,3 +5,5 @@
 #### **--health-retries**=*retries*
 
 The number of retries allowed before a healthcheck is considered to be unhealthy. The default value is **3**.
+
+Note: This parameter can overwrite the healthcheck configuration from the image.
diff --git a/docs/source/markdown/options/health-start-period.md b/docs/source/markdown/options/health-start-period.md
index 4391338e461..302af88072b 100644
--- a/docs/source/markdown/options/health-start-period.md
+++ b/docs/source/markdown/options/health-start-period.md
@@ -12,3 +12,5 @@ the container's health state will be updated to `healthy`. However, if the healt
 stay as `starting` until either the health check is successful or until the `--health-start-period` time is over. If the
 health check command fails after the `--health-start-period` time is over, the health state will be updated to `unhealthy`.
 The health check command is executed periodically based on the value of `--health-interval`.
+
+Note: This parameter will overwrite related healthcheck configuration from the image.
diff --git a/docs/source/markdown/options/health-timeout.md b/docs/source/markdown/options/health-timeout.md
index 98d807ce5c1..4adbdb1e907 100644
--- a/docs/source/markdown/options/health-timeout.md
+++ b/docs/source/markdown/options/health-timeout.md
@@ -10,3 +10,5 @@ value can be expressed in a time format such as **1m22s**. The default value is
 Note: A timeout marks the healthcheck as failed but does not terminate the running process.
 This ensures that a slow but eventually successful healthcheck does not disrupt the container
 but is still accounted for in the health status.
+
+Note: This parameter will overwrite related healthcheck configuration from the image.
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
index d5973e69840..b9fcb746e37 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/containers/common/libimage"
 	"github.com/containers/common/pkg/config"
+	"github.com/containers/image/v5/manifest"
 	"github.com/containers/podman/v5/libpod"
 	"github.com/containers/podman/v5/libpod/define"
 	ann "github.com/containers/podman/v5/pkg/annotations"
@@ -61,6 +62,66 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	if overrideHealthCheckConfig != nil {
+		if overrideHealthCheckConfig.Interval != 0 {
+			s.HealthConfig.Interval = overrideHealthCheckConfig.Interval
+		}
+		if overrideHealthCheckConfig.Retries != 0 {
+			s.HealthConfig.Retries = overrideHealthCheckConfig.Retries
+		}
+		if overrideHealthCheckConfig.Timeout != 0 {
+			s.HealthConfig.Timeout = overrideHealthCheckConfig.Timeout
+		}
+		if overrideHealthCheckConfig.StartPeriod != 0 {
+			s.HealthConfig.StartPeriod = overrideHealthCheckConfig.StartPeriod
+		}
+	}
+
+	disableInterval := false
+	if s.HealthConfig.Interval < 0 {
+		s.HealthConfig.Interval = 0
+		disableInterval = true
+	}
+
+	// NOTE: Zero means inherit.
+	if s.HealthConfig.Timeout == 0 {
+		hct, err := time.ParseDuration(define.DefaultHealthCheckTimeout)
+		if err != nil {
+			return err
+		}
+		s.HealthConfig.Timeout = hct
+	}
+	if s.HealthConfig.Interval == 0 && !disableInterval {
+		hct, err := time.ParseDuration(define.DefaultHealthCheckInterval)
+		if err != nil {
+			return err
+		}
+		s.HealthConfig.Interval = hct
+	}
+
+	if s.HealthConfig.Retries == 0 {
+		s.HealthConfig.Retries = int(define.DefaultHealthCheckRetries)
+	}
+
+	if s.HealthConfig.StartPeriod == 0 {
+		hct, err := time.ParseDuration(define.DefaultHealthCheckStartPeriod)
+		if err != nil {
+			return err
+		}
+		s.HealthConfig.StartPeriod = hct
+	}
+
+	return nil
+}
+
 // Fill any missing parts of the spec generator (e.g. from the image).
 // Returns a set of warnings or any fatal error that occurred.
 func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) ([]string, error) {
@@ -70,25 +131,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
 		return nil, err
 	}
 	if inspectData != nil {
-		if s.HealthConfig == nil {
-			// NOTE: the health check is only set for Docker images
-			// but inspect will take care of it.
-			s.HealthConfig = inspectData.HealthCheck
-			if s.HealthConfig != nil {
-				if s.HealthConfig.Timeout == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckTimeout)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Timeout = hct
-				}
-				if s.HealthConfig.Interval == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckInterval)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Interval = hct
-				}
+		if s.HealthConfig == nil || len(s.HealthConfig.Test) == 0 {
+			if err := applyHealthCheckOverrides(s, inspectData.HealthCheck); err != nil {
+				return nil, err
 			}
 		}
 
diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go
index b65419569fc..97b5098ef6f 100644
--- a/test/e2e/healthcheck_run_test.go
+++ b/test/e2e/healthcheck_run_test.go
@@ -32,6 +32,20 @@ var _ = Describe("Podman healthcheck run", func() {
 		Expect(hc).Should(ExitWithError(125, "has no defined healthcheck"))
 	})
 
+	It("podman run/create override image healthcheck configuration", func() {
+		podmanTest.PodmanExitCleanly("run", "-dt", "--name", "hc", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", HEALTHCHECK_IMAGE)
+		hc := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc")
+		Expect(hc.OutputToString()).To(Equal("10s--10s--10s--2"))
+
+		podmanTest.PodmanExitCleanly("create", "-q", "--name", "hc1", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", "quay.io/libpod/healthcheck:config-only", "ls")
+		hc1 := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc1")
+		Expect(hc1.OutputToString()).To(Equal("10s--10s--10s--2"))
+
+		podmanTest.PodmanExitCleanly("run", "-dt", "--name", "hc2", "--health-start-period", "10s", "--health-interval", "disable", "--health-timeout", "10s", "--health-retries", "2", HEALTHCHECK_IMAGE)
+		hc2 := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc2")
+		Expect(hc2.OutputToString()).To(Equal("10s--0s--10s--2"))
+	})
+
 	It("podman disable healthcheck with --no-healthcheck must not show starting on status", func() {
 		session := podmanTest.Podman([]string{"run", "-dt", "--no-healthcheck", "--name", "hc", HEALTHCHECK_IMAGE})
 		session.WaitWithDefaultTimeout()
@@ -70,7 +84,7 @@ var _ = Describe("Podman healthcheck run", func() {
 		hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Config.Healthcheck}}", "hc"})
 		hc.WaitWithDefaultTimeout()
 		Expect(hc).Should(ExitCleanly())
-		Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 0s 5m0s 3s 0}"))
+		Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 0s 5m0s 3s 3}"))
 	})
 
 	It("podman disable healthcheck with --health-cmd=none on valid container", func() {


Reviews:
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:58:53Z
user: login: mheon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T15:09:17Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T16:27:28Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T17:15:32Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T10:03:28Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:22:52Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:28:37Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:30:57Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:31:04Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:36:15Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:48:33Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T16:41:43Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T18:08:34Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T18:12:28Z
user: login: Luap99
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T12:38:12Z
user: login: mheon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T12:41:51Z
user: login: mheon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T12:42:19Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:46:01Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:49:41Z
user: login: Honny1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:54:40Z

Review comments:
diff_hunk: @@ -615,6 +615,8 @@ type ContainerHealthCheckConfig struct {
 	// ("0" value means an infinite log length).
 	// TODO (6.0): In next major release convert it to pointer and use omitempty
 	HealthMaxLogSize uint `json:"healthMaxLogSize"`
+	// HealthImageOverride is a map of healthcheck flags that overrides healthcheck configuration from image.
+	HealthImageOverride map[string]string `json:"HealthImageOverride,omitempty"`
path: pkg/specgen/specgen.go
user: login: Luap99
body: I am not sure this is the right way to go about. specgen is an external API commitment and we should offer a "sane" json payload for external users. It seems totally confusing to me that there are multiple healtchchecks settings and you need to use one or the other, etc...
cc @mheon 


I don't get why you couldn't just use the existing data to pass it to the server.



created_at: 2025-03-26T14:58:53Z
author_association: MEMBER
diff_hunk: @@ -615,6 +615,8 @@ type ContainerHealthCheckConfig struct {
 	// ("0" value means an infinite log length).
 	// TODO (6.0): In next major release convert it to pointer and use omitempty
 	HealthMaxLogSize uint `json:"healthMaxLogSize"`
+	// HealthImageOverride is a map of healthcheck flags that overrides healthcheck configuration from image.
+	HealthImageOverride map[string]string `json:"HealthImageOverride,omitempty"`
path: pkg/specgen/specgen.go
user: login: mheon
body: Tend to concur - Specgen is part of the public container creation API, so we should avoid things that cause confusion where possible. We already have all Healthcheck info in Specgen, so using that instead seems preferable.
created_at: 2025-03-26T15:09:15Z
author_association: MEMBER
diff_hunk: @@ -615,6 +615,8 @@ type ContainerHealthCheckConfig struct {
 	// ("0" value means an infinite log length).
 	// TODO (6.0): In next major release convert it to pointer and use omitempty
 	HealthMaxLogSize uint `json:"healthMaxLogSize"`
+	// HealthImageOverride is a map of healthcheck flags that overrides healthcheck configuration from image.
+	HealthImageOverride map[string]string `json:"HealthImageOverride,omitempty"`
path: pkg/specgen/specgen.go
user: login: Honny1
body: It's a chicken or the egg problem. First, the values for the Healtcheck config are set from the command line, if there is no Healthcheck command(`--health-cmd`), the other flags are not set. The next step is to get and set Healthcheck from the image if it is not already set.

I could create a dummy Healthcheck config, which is used to overwrite the values from the dummy Healthcheck to avoid changes in the `spacgen`. 
created_at: 2025-03-26T16:27:27Z
author_association: MEMBER
diff_hunk: @@ -615,6 +615,8 @@ type ContainerHealthCheckConfig struct {
 	// ("0" value means an infinite log length).
 	// TODO (6.0): In next major release convert it to pointer and use omitempty
 	HealthMaxLogSize uint `json:"healthMaxLogSize"`
+	// HealthImageOverride is a map of healthcheck flags that overrides healthcheck configuration from image.
+	HealthImageOverride map[string]string `json:"HealthImageOverride,omitempty"`
path: pkg/specgen/specgen.go
user: login: Luap99
body: > I could create a dummy Healthcheck config, which is used to overwrite the values from the dummy Healthcheck to avoid changes in the spacgen.

I think that should be how it works as this seems more logical to users. We basically want overwrite each option one by one and not as a whole on the client. 
created_at: 2025-03-26T17:15:32Z
author_association: MEMBER
diff_hunk: @@ -61,6 +63,62 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	disableInterval := false
+	if overrideHealthCheckConfig != nil {
+		disableInterval = slices.Contains(overrideHealthCheckConfig.Test, define.HealthConfigTestOverrideDisableInterval)
path: pkg/specgen/generate/container.go
user: login: Luap99
body: These special values still feel totally wrong and I don't get why they ar eneeded at all, this should just work without it.




created_at: 2025-03-27T10:00:33Z
author_association: MEMBER
diff_hunk: @@ -70,25 +128,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
 		return nil, err
 	}
 	if inspectData != nil {
-		if s.HealthConfig == nil {
-			// NOTE: the health check is only set for Docker images
-			// but inspect will take care of it.
-			s.HealthConfig = inspectData.HealthCheck
-			if s.HealthConfig != nil {
-				if s.HealthConfig.Timeout == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckTimeout)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Timeout = hct
-				}
-				if s.HealthConfig.Interval == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckInterval)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Interval = hct
-				}
+		if s.HealthConfig == nil || slices.Contains(s.HealthConfig.Test, define.HealthConfigTestOverride) {
path: pkg/specgen/generate/container.go
user: login: Luap99
body: same here we should not just make up new special values
created_at: 2025-03-27T10:00:55Z
author_association: MEMBER
diff_hunk: @@ -1053,3 +1057,46 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
path: cmd/podman/common/create.go
user: login: Luap99
body: I don't understand the need for a new function. IMO it should also just be in MakeHealthCheckFromCli() and it needs to be fixed there to always work when only one option is set.
created_at: 2025-03-27T10:03:21Z
author_association: MEMBER
diff_hunk: @@ -1053,3 +1057,46 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
path: cmd/podman/common/create.go
user: login: Honny1
body: I think this is problematic because the `FillOutSpecGen()` function that calls the `MakeHealthCheckFromCli()` function has no idea which flags have been changed, so it uses the default or command line value. Here, we have no option to determine whether the user has set a default value or whether it is the default value with no change.  And the values specified by the flags take precedence, so they override the configuration from the image. 
created_at: 2025-03-27T13:22:52Z
author_association: MEMBER
diff_hunk: @@ -61,6 +63,62 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	disableInterval := false
+	if overrideHealthCheckConfig != nil {
+		disableInterval = slices.Contains(overrideHealthCheckConfig.Test, define.HealthConfigTestOverrideDisableInterval)
path: pkg/specgen/generate/container.go
user: login: Honny1
body: Because if you set `--health-interval disable`, it will be converted to `0` in the hc configuration. In the hc config `0` means the default value, so in the next steps this value will be overwritten by the default value. I want to avoid this override.
created_at: 2025-03-27T13:28:37Z
author_association: MEMBER
diff_hunk: @@ -70,25 +128,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
 		return nil, err
 	}
 	if inspectData != nil {
-		if s.HealthConfig == nil {
-			// NOTE: the health check is only set for Docker images
-			// but inspect will take care of it.
-			s.HealthConfig = inspectData.HealthCheck
-			if s.HealthConfig != nil {
-				if s.HealthConfig.Timeout == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckTimeout)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Timeout = hct
-				}
-				if s.HealthConfig.Interval == 0 {
-					hct, err := time.ParseDuration(define.DefaultHealthCheckInterval)
-					if err != nil {
-						return nil, err
-					}
-					s.HealthConfig.Interval = hct
-				}
+		if s.HealthConfig == nil || slices.Contains(s.HealthConfig.Test, define.HealthConfigTestOverride) {
path: pkg/specgen/generate/container.go
user: login: Honny1
body: I could use an empty `s.HealthConfig.Test`, but that would not solve the problem with `--health-interval disable`.
created_at: 2025-03-27T13:30:57Z
author_association: MEMBER
diff_hunk: @@ -1053,3 +1057,46 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
path: cmd/podman/common/create.go
user: login: Luap99
body: Then the command line should not pass in any default and just empty strings I would say.

This whole thing of the command line sets the default must stop long term anyway IMO. It causes nothing but issues like this.
created_at: 2025-03-27T13:31:03Z
author_association: MEMBER
diff_hunk: @@ -1053,3 +1057,46 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
path: cmd/podman/common/create.go
user: login: Honny1
body: So can I change the default flag settings? Is this an allowed API change? Or reset them to empty values when they are not used in the command line? 
created_at: 2025-03-27T13:36:15Z
author_association: MEMBER
diff_hunk: @@ -61,6 +63,62 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	disableInterval := false
+	if overrideHealthCheckConfig != nil {
+		disableInterval = slices.Contains(overrideHealthCheckConfig.Test, define.HealthConfigTestOverrideDisableInterval)
path: pkg/specgen/generate/container.go
user: login: Luap99
body: Odd, why is "disable" even a thing for interval?
`--health-interval disable` disabled the healtcheck? We also have --no-healthcheck for this.

A duration can be negative would it be possible for us to pass `-1` instead to mean disable? I guess the issue here is that this would break the client <-> server interface, e.g. if you use a new client against and old server this will likely result in something unexpected. However the same concern is for your special value in the cmd as the server would also not understand it.

I am not sure what is best here.
created_at: 2025-03-27T13:48:33Z
author_association: MEMBER
diff_hunk: @@ -61,6 +63,62 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	disableInterval := false
+	if overrideHealthCheckConfig != nil {
+		disableInterval = slices.Contains(overrideHealthCheckConfig.Test, define.HealthConfigTestOverrideDisableInterval)
path: pkg/specgen/generate/container.go
user: login: Honny1
body: Zero was always represented as a forbidden interval. So I'll leave it at that, no one is complaining that zero for an interval doesn't mean it's the default value. But for command line processing, use `-1` and then set it to `0`. Maybe `//TODO: v6.0`?

The `--health-interval disable` will disable the timer, but you can still run it with `podman healthcheck run`. The `--no-healthcheck` disables the entire healthcheck configuration, so you cannot run it with `podman healthcheck run`.
created_at: 2025-03-27T16:41:43Z
author_association: MEMBER
diff_hunk: @@ -61,6 +63,62 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen
 	return image, resolvedName, inspectData, err
 }
 
+func applyHealthCheckOverrides(s *specgen.SpecGenerator, healthCheckFromImage *manifest.Schema2HealthConfig) error {
+	overrideHealthCheckConfig := s.HealthConfig
+	s.HealthConfig = healthCheckFromImage
+
+	if s.HealthConfig == nil {
+		return nil
+	}
+
+	disableInterval := false
+	if overrideHealthCheckConfig != nil {
+		disableInterval = slices.Contains(overrideHealthCheckConfig.Test, define.HealthConfigTestOverrideDisableInterval)
path: pkg/specgen/generate/container.go
user: login: Luap99
body: > But for command line processing, use -1 and then set it to 0. Maybe //TODO: v6.0?

Yes that seems reasonable.

> The --health-interval disable will disable the timer, but you can still run it with podman healthcheck run. The --no-healthcheck disables the entire healthcheck configuration, so you cannot run it with podman healthcheck run.

Thanks, that was the piece I was missing. I doubt it matters in reality but at least I see the difference clearly now.
created_at: 2025-03-27T18:08:31Z
author_association: MEMBER
diff_hunk: @@ -1053,3 +1057,46 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
 	)
 	_ = cmd.RegisterFlagCompletionFunc(blkioWeightDeviceFlagName, completion.AutocompleteDefault)
 }
+
+func GetHealthCheckOverrideConfig(cmd *cobra.Command, vals *entities.ContainerCreateOptions) (*manifest.Schema2HealthConfig, error) {
path: cmd/podman/common/create.go
user: login: Luap99
body: Yeah I would leave the cli default value empty and then set the default at a later stage, i.e. on the server.
The only thing about the setting is on the client is that podman run --help shows the default value. IMO that is not worth the troubles it has caused us.
created_at: 2025-03-27T18:12:28Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,20 @@ var _ = Describe("Podman healthcheck run", func() {
 		Expect(hc).Should(ExitWithError(125, "has no defined healthcheck"))
 	})
 
+	It("podman run/create override image healthcheck configuration", func() {
+		podmanTest.PodmanExitCleanly("run", "-dt", "--name", "hc", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", HEALTHCHECK_IMAGE)
+		hc := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc")
+		Expect(hc.OutputToString()).To(Equal("10s--10s--10s--2"))
+
+		podmanTest.PodmanExitCleanly("create", "-q", "--name", "hc1", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", "quay.io/libpod/healthcheck:config-only", "ls")
path: test/e2e/healthcheck_run_test.go
user: login: Luap99
body: I am confused where `quay.io/libpod/healthcheck:config-only` comes from.

It is only used in one other test, I understand this may value for your test and since it is used already it should not do harm but I find the random string us here rather weird.
I looked into our caching logic and this image is not cached with mean this can cause flakes. Regardless I am fine to merge this, I work on adding this into our cache registry.


created_at: 2025-04-03T12:38:00Z
author_association: MEMBER
diff_hunk: @@ -10,3 +10,5 @@ to be applied. A value of **none** disables existing healthchecks.
 
 Multiple options can be passed in the form of a JSON array; otherwise, the command is interpreted
 as an argument to **/bin/sh -c**.
+
+Note: The Healthcheck does not inherit the configuration form image.
path: docs/source/markdown/options/health-cmd.md
user: login: mheon
body: Is this only if the `--health-cmd` option is passed? I'm a little confused as to what this means
created_at: 2025-04-03T12:41:51Z
author_association: MEMBER
diff_hunk: @@ -5,3 +5,5 @@
 #### **--health-interval**=*interval*
 
 Set an interval for the healthchecks. An _interval_ of **disable** results in no automatic timer setup. The default is **30s**.
+
+Note: This parameter can overwrite the healthcheck configuration from the image.
path: docs/source/markdown/options/health-interval.md
user: login: mheon
body: Isn't it safer to say it will override it? Is there a case where it won't be overridden?
created_at: 2025-04-03T12:42:19Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,20 @@ var _ = Describe("Podman healthcheck run", func() {
 		Expect(hc).Should(ExitWithError(125, "has no defined healthcheck"))
 	})
 
+	It("podman run/create override image healthcheck configuration", func() {
+		podmanTest.PodmanExitCleanly("run", "-dt", "--name", "hc", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", HEALTHCHECK_IMAGE)
+		hc := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc")
+		Expect(hc.OutputToString()).To(Equal("10s--10s--10s--2"))
+
+		podmanTest.PodmanExitCleanly("create", "-q", "--name", "hc1", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", "quay.io/libpod/healthcheck:config-only", "ls")
path: test/e2e/healthcheck_run_test.go
user: login: Luap99
body: (I take that back the image is already cached in our registry so all should be fine)
created_at: 2025-04-03T13:46:00Z
author_association: MEMBER
diff_hunk: @@ -10,3 +10,5 @@ to be applied. A value of **none** disables existing healthchecks.
 
 Multiple options can be passed in the form of a JSON array; otherwise, the command is interpreted
 as an argument to **/bin/sh -c**.
+
+Note: The Healthcheck does not inherit the configuration form image.
path: docs/source/markdown/options/health-cmd.md
user: login: Honny1
body: The default values are used when using the `--helath-cmd`  flag. The default values are used even if Healthcheck is configured in the image. 

With other flags, you can change the configuration of the Healthcheck that is baked in the image. For example, just override the interval of Healthcheck. 

I will reword the change in the documentation.
created_at: 2025-04-03T13:49:40Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,20 @@ var _ = Describe("Podman healthcheck run", func() {
 		Expect(hc).Should(ExitWithError(125, "has no defined healthcheck"))
 	})
 
+	It("podman run/create override image healthcheck configuration", func() {
+		podmanTest.PodmanExitCleanly("run", "-dt", "--name", "hc", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", HEALTHCHECK_IMAGE)
+		hc := podmanTest.PodmanExitCleanly("container", "inspect", "--format", "{{.Config.Healthcheck.StartPeriod}}--{{.Config.Healthcheck.Interval}}--{{.Config.Healthcheck.Timeout}}--{{.Config.Healthcheck.Retries}}", "hc")
+		Expect(hc.OutputToString()).To(Equal("10s--10s--10s--2"))
+
+		podmanTest.PodmanExitCleanly("create", "-q", "--name", "hc1", "--health-start-period", "10s", "--health-interval", "10s", "--health-timeout", "10s", "--health-retries", "2", "quay.io/libpod/healthcheck:config-only", "ls")
path: test/e2e/healthcheck_run_test.go
user: login: Honny1
body: I wanted to use some image that contains a healthcheck. I used it because it was already used. I have a feeling there could be a lot of images that are not cached. 
created_at: 2025-04-03T13:54:40Z
author_association: MEMBER

PR comments:
user: login: jankaluza
created_at: 2025-03-26T10:59:17Z
body: I think this is good PR. I was thinking maybe the newly added code in the CompleteSpec could exist in some helper function like ApplyHealthCheckOverrides to not make the CompleteSpec too long, but that's nitpick and definitely not a blocker.

I would say LGTM, but since new to team, let's wait for someone more knowledgeable :-). 
author_association: MEMBER
user: login: Honny1
created_at: 2025-03-26T11:08:05Z
body: Good idea. I put it in a separate function. 
author_association: MEMBER
user: login: Honny1
created_at: 2025-03-27T16:51:14Z
body: @Luap99 I got rid of the special value, but I'm not sure about `GetHealthCheckOverrideConfig()`.
author_association: MEMBER
user: login: Luap99
created_at: 2025-03-27T18:10:38Z
body: > @Luap99 I got rid of the special value, but I'm not sure about GetHealthCheckOverrideConfig().

Ack, I have proper look tomorrow, right now I cannot think of anything better to avoid this then. I guess I might have to live with it.
author_association: MEMBER
user: login: Honny1
created_at: 2025-04-01T16:19:20Z
body: PTAL @Luap99 
author_association: MEMBER
user: login: openshift-ci[bot]
created_at: 2025-04-03T12:38:19Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/containers/podman/pull/25676#" title="Author self-approved">Honny1</a>*, *<a href="https://github.com/containers/podman/pull/25676#pullrequestreview-2739697269" title="Approved">Luap99</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=containers%2Fpodman).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/containers/podman/blob/main/OWNERS)~~ [Luap99]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: mheon
created_at: 2025-04-03T15:28:01Z
body: /lgtm
author_association: MEMBER
user: login: travier
created_at: 2025-04-04T13:55:10Z
body: Thanks!
author_association: MEMBER