Repository: containerd/nerdctl

Issue:
Add support for regexp in `--filter name=<value>`
### What is the problem you're trying to solve

In the docker CLI, [filters can use regexp](https://github.com/moby/moby/blob/bf9d73956183e09da218cc9a05b1f29fb8b25d53/api/types/filters/parse.go#L179-L196). However, in nerdctl, the filters generally need to be an exact match.

For example, using the `name` filter in docker:

```
❯ docker ps
❯ docker ps --filter name="foo-"
CONTAINER ID   IMAGE     COMMAND           CREATED          STATUS          PORTS     NAMES
9dd7fabec4fc   alpine    "sleep 1000000"   14 seconds ago   Up 14 seconds             foo-bar
❯ docker ps --filter name="foo-.*"
CONTAINER ID   IMAGE     COMMAND           CREATED          STATUS          PORTS     NAMES
9dd7fabec4fc   alpine    "sleep 1000000"   22 seconds ago   Up 22 seconds             foo-bar
```

And in nerdctl:

```
❯ nerdctl ps
CONTAINER ID    IMAGE                              COMMAND            CREATED               STATUS    PORTS    NAMES
d21c34b4b65a    docker.io/library/alpine:latest    "sleep 1000000"    About a minute ago    Up                 
❯ nerdctl ps --filter name="foo-"
CONTAINER ID    IMAGE                              COMMAND            CREATED               STATUS    PORTS    NAMES
d21c34b4b65a    docker.io/library/alpine:latest    "sleep 1000000"    About a minute ago    Up                 foo-bar
❯ nerdctl ps --filter name="foo-.*"
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
```

It would be nice to be able to use regexp in filters, or even to check for an *exact* match (AFAICT, that's not possible based on the logic [here](https://github.com/containerd/nerdctl/blob/65f4412ea5058de79856d59d21fbe94b042f6f16/pkg/cmd/container/list_util.go#L166-L174)).

### Describe the solution you'd like

At least the various `name` filters in different places (specifically `container/list.go` and `volume/list.go`) should support regexp, not sure about the others (maybe labels?).

Not *entirely* sure what the backwards compatibility guidelines are, since just switching the logic would be a backwards incompatible change.

### Additional context

Would be happy to throw together a patch if this sounds good!

Hints from issue comments:
@jpadams: I'd be happy to see this for `nerdctl ps --filter` at least.

cc @pendo324 @apostasie: Curious if you folks use nerdctl at Dagger?@apostasie: > Not _entirely_ sure what the backwards compatibility guidelines are, since just switching the logic would be a backwards incompatible change.

@jedevc IMO it is fine to break backward compatibility IF we are aligning with docker behavior (not being docker compatible is usually considered a bug).

Wanna go for it? Alternatively I can look into it too.@jedevc: We don't does nerdctl directly, but we do a couple of shell invocations of the docker cli to provision the dagger engine (sort of like the moby `connhelpers`).

We want to support whatever environments users are using! 🎉 and increasingly often, that's nerdctl (through tooling like finch, though also standalone). 

> Wanna go for it? Alternatively I can look into it too.

Yeah, I'll take a look! I haven't had a chance to work on anything in nerdctl yet, so it'll be a fun excuse 😁@apostasie: Go for it! :-)

Tentatively, something like that would do just fine for `foldNameFilter` (I believe that would still be backward compatible):
```golang
func (cl *containerFilterContext) foldNameFilter(_ context.Context, filter, value string) error {
	cl.nameFilterFuncs = append(cl.nameFilterFuncs, func(name string) bool {
		if value == "" || name == value {
			return true
		}
		match, _ := regexp.MatchString(value, name)
		return match
	})
	return nil
}
```

Of course, we should have it wherever Docker supports it (not just name), and yeah, we need tests :-)

Solution patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b68c6395..1881d185 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -54,7 +54,7 @@ jobs:
         Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE])
         EOF
     - name: "Generate artifact attestation"
-      uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd  # v2.3.0
+      uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be  # v2.4.0
       if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
       with:
         subject-path: _output/*
diff --git a/.golangci.yml b/.golangci.yml
index 483315a9..d7e9204a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -126,7 +126,7 @@ linters:
           # 222 occurrences. Could indicate failure to handle broken conditions.
           disabled: true
         - name: cognitive-complexity
-          arguments: [197]
+          arguments: [205]
           # 441 occurrences (at default 7). We should try to lower it (involves significant refactoring).
 
         ##### P2: nice to have.
diff --git a/cmd/nerdctl/container/container.go b/cmd/nerdctl/container/container.go
index 2d92f8d5..6188e701 100644
--- a/cmd/nerdctl/container/container.go
+++ b/cmd/nerdctl/container/container.go
@@ -54,6 +54,7 @@ func Command() *cobra.Command {
 		pruneCommand(),
 		StatsCommand(),
 		AttachCommand(),
+		HealthCheckCommand(),
 	)
 	AddCpCommand(cmd)
 	return cmd
diff --git a/cmd/nerdctl/container/container_create.go b/cmd/nerdctl/container/container_create.go
index 62ceb96a..83f377eb 100644
--- a/cmd/nerdctl/container/container_create.go
+++ b/cmd/nerdctl/container/container_create.go
@@ -258,6 +258,40 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
 	}
 	// #endregion
 
+	// #region for healthcheck flags
+	opt.HealthCmd, err = cmd.Flags().GetString("health-cmd")
+	if err != nil {
+		return opt, err
+	}
+	opt.HealthInterval, err = cmd.Flags().GetDuration("health-interval")
+	if err != nil {
+		return opt, err
+	}
+	opt.HealthTimeout, err = cmd.Flags().GetDuration("health-timeout")
+	if err != nil {
+		return opt, err
+	}
+	opt.HealthRetries, err = cmd.Flags().GetInt("health-retries")
+	if err != nil {
+		return opt, err
+	}
+	opt.HealthStartPeriod, err = cmd.Flags().GetDuration("health-start-period")
+	if err != nil {
+		return opt, err
+	}
+	opt.HealthStartInterval, err = cmd.Flags().GetDuration("health-start-interval")
+	if err != nil {
+		return opt, err
+	}
+	opt.NoHealthcheck, err = cmd.Flags().GetBool("no-healthcheck")
+	if err != nil {
+		return opt, err
+	}
+	if err := helpers.ValidateHealthcheckFlags(opt); err != nil {
+		return opt, err
+	}
+	// #endregion
+
 	// #region for intel RDT flags
 	opt.RDTClass, err = cmd.Flags().GetString("rdt-class")
 	if err != nil {
diff --git a/cmd/nerdctl/container/container_health_check.go b/cmd/nerdctl/container/container_health_check.go
new file mode 100644
index 00000000..abc03371
--- /dev/null
+++ b/cmd/nerdctl/container/container_health_check.go
@@ -0,0 +1,85 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package container
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	containerd "github.com/containerd/containerd/v2/client"
+
+	"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
+	"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
+	"github.com/containerd/nerdctl/v2/pkg/clientutil"
+	"github.com/containerd/nerdctl/v2/pkg/cmd/container"
+	"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
+)
+
+// HealthCheckCommand returns a cobra command for `nerdctl container healthcheck`
+func HealthCheckCommand() *cobra.Command {
+	var healthCheckCommand = &cobra.Command{
+		Use:               "healthcheck [flags] CONTAINER",
+		Short:             "Execute the health check command in a container",
+		Args:              cobra.ExactArgs(1),
+		RunE:              healthCheckAction,
+		ValidArgsFunction: healthCheckShellComplete,
+		SilenceUsage:      true,
+		SilenceErrors:     true,
+	}
+
+	return healthCheckCommand
+}
+
+func healthCheckAction(cmd *cobra.Command, args []string) error {
+	globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
+	if err != nil {
+		return err
+	}
+
+	client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)
+	if err != nil {
+		return err
+	}
+	defer cancel()
+
+	containerID := args[0]
+	walker := &containerwalker.ContainerWalker{
+		Client: client,
+		OnFound: func(ctx context.Context, found containerwalker.Found) error {
+			if found.MatchCount > 1 {
+				return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
+			}
+			return container.HealthCheck(ctx, client, found.Container)
+		},
+	}
+
+	n, err := walker.Walk(ctx, containerID)
+	if err != nil {
+		return err
+	} else if n == 0 {
+		return fmt.Errorf("no such container %s", containerID)
+	}
+	return nil
+}
+
+func healthCheckShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return completion.ContainerNames(cmd, func(status containerd.ProcessStatus) bool {
+		return status == containerd.Running
+	})
+}
diff --git a/cmd/nerdctl/container/container_run.go b/cmd/nerdctl/container/container_run.go
index be629b7e..39f1004a 100644
--- a/cmd/nerdctl/container/container_run.go
+++ b/cmd/nerdctl/container/container_run.go
@@ -234,6 +234,15 @@ func setCreateFlags(cmd *cobra.Command) {
 	// rootfs flags (from Podman)
 	cmd.Flags().Bool("rootfs", false, "The first argument is not an image but the rootfs to the exploded container")
 
+	// Health check flags
+	cmd.Flags().String("health-cmd", "", "Command to run to check health")
+	cmd.Flags().Duration("health-interval", 0, "Time between running the check (default: 30s)")
+	cmd.Flags().Duration("health-timeout", 0, "Maximum time to allow one check to run (default: 30s)")
+	cmd.Flags().Int("health-retries", 0, "Consecutive failures needed to report unhealthy (default: 3)")
+	cmd.Flags().Duration("health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown")
+	cmd.Flags().Duration("health-start-interval", 0, "Time between running the checks during the start period")
+	cmd.Flags().Bool("no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
+
 	// #region env flags
 	// entrypoint needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"}
 	// entrypoint StringArray is an internal implementation to support `nerdctl compose` entrypoint yaml filed with multiple strings
diff --git a/cmd/nerdctl/helpers/flagutil.go b/cmd/nerdctl/helpers/flagutil.go
index 4f9261c0..9c4e8c01 100644
--- a/cmd/nerdctl/helpers/flagutil.go
+++ b/cmd/nerdctl/helpers/flagutil.go
@@ -46,6 +46,39 @@ func VerifyOptions(cmd *cobra.Command) (opt types.ImageVerifyOptions, err error)
 	return
 }
 
+func ValidateHealthcheckFlags(options types.ContainerCreateOptions) error {
+	healthFlagsSet :=
+		options.HealthInterval != 0 ||
+			options.HealthTimeout != 0 ||
+			options.HealthRetries != 0 ||
+			options.HealthStartPeriod != 0 ||
+			options.HealthStartInterval != 0
+
+	if options.NoHealthcheck {
+		if options.HealthCmd != "" || healthFlagsSet {
+			return fmt.Errorf("--no-healthcheck conflicts with --health-* options")
+		}
+	}
+
+	// Note: HealthCmd can be empty with other healthcheck flags set cause healthCmd could be coming from image.
+	if options.HealthInterval < 0 {
+		return fmt.Errorf("--health-interval cannot be negative")
+	}
+	if options.HealthTimeout < 0 {
+		return fmt.Errorf("--health-timeout cannot be negative")
+	}
+	if options.HealthRetries < 0 {
+		return fmt.Errorf("--health-retries cannot be negative")
+	}
+	if options.HealthStartPeriod < 0 {
+		return fmt.Errorf("--health-start-period cannot be negative")
+	}
+	if options.HealthStartInterval < 0 {
+		return fmt.Errorf("--health-start-interval cannot be negative")
+	}
+	return nil
+}
+
 func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) {
 	debug, err := cmd.Flags().GetBool("debug")
 	if err != nil {
diff --git a/cmd/nerdctl/main.go b/cmd/nerdctl/main.go
index 88d753a1..d04f54bd 100644
--- a/cmd/nerdctl/main.go
+++ b/cmd/nerdctl/main.go
@@ -287,6 +287,7 @@ Config file ($NERDCTL_TOML): %s
 		container.WaitCommand(),
 		container.RenameCommand(),
 		container.AttachCommand(),
+		container.HealthCheckCommand(),
 		// #endregion
 
 		// Build
diff --git a/docs/command-reference.md b/docs/command-reference.md
index a4173d8f..8b10bd4b 100644
--- a/docs/command-reference.md
+++ b/docs/command-reference.md
@@ -321,6 +321,16 @@ Metadata flags:
 - :whale: :blue_square: `--cidfile`: Write the container ID to the file
 - :nerd_face: `--pidfile`: file path to write the task's pid. The CLI syntax conforms to Podman convention.
 
+Health check flags:
+
+- :whale: :blue_square: `--health-cmd`: Command to run to check container health
+- :whale: :blue_square: `--health-interval`: Time between running the check (e.g., 30s, 1m)
+- :whale: :blue_square: `--health-timeout`: Time to wait before considering the check failed (e.g., 5s)
+- :whale: :blue_square: `--health-retries`: Number of failures before container is considered unhealthy
+- :whale: :blue_square: `--health-start-period`: Start period for the container to initialize before starting health-retries countdown
+- :whale: :blue_square: `--health-start-interval`: Interval between checks during the start period
+- :whale: :blue_square: `--no-healthcheck`: Disable any health checks defined by image or CLI
+
 Logging flags:
 
 - :whale: `--log-driver=(json-file|journald|fluentd|syslog|none)`: Logging driver for the container (default `json-file`).
@@ -428,7 +438,7 @@ IPFS flags:
 - :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)
 
 Unimplemented `docker run` flags:
-    `--device-cgroup-rule`, `--disable-content-trust`, `--expose`, `--health-*`, `--isolation`, `--no-healthcheck`,
+    `--device-cgroup-rule`, `--disable-content-trust`, `--expose`, `--isolation`,
     `--link*`, `--publish-all`, `--storage-opt`, `--volume-driver`
 
 ### :whale: :blue_square: nerdctl exec
diff --git a/docs/healthchecks.md b/docs/healthchecks.md
new file mode 100644
index 00000000..b47c9274
--- /dev/null
+++ b/docs/healthchecks.md
@@ -0,0 +1,51 @@
+# Health Check Support in nerdctl
+
+`nerdctl` supports Docker-compatible health checks for containers, allowing users to monitor container health via a user-defined command.
+
+Currently, health checks can be triggered manually using the nerdctl container healthcheck command. Automatic orchestration (e.g., periodic checks) will be added in a future update.
+
+Health checks can be configured in multiple ways:
+
+1. At container creation time using nerdctl run or nerdctl create with `--health-*` flags
+2. At image build time using HEALTHCHECK in a Dockerfile
+3. In docker-compose.yaml files, if using nerdctl compose
+
+When a container is created, nerdctl determines the health check configuration based on the following priority:
+
+1. **CLI flags** take highest precedence (e.g., `--health-cmd`, etc.)
+2. If no CLI flags are set, nerdctl will use any health check defined in the image.
+3. If neither is present, no health check will be configured
+
+Example:
+
+```bash
+nerdctl run --name web --health-cmd="curl -f http://localhost || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 nginx
+```
+
+### Disabling Health Checks
+
+You can disable health checks using the following flag during container create/run:
+
+```bash
+--no-healthcheck
+```
+
+### Running Health Checks Manually
+
+nerdctl provides a container healthcheck command that can be manually triggered by the user. This command runs the
+configured health check inside the container and reports the result. It serves as the entry point for executing
+health checks, especially in scenarios where external scheduling is used.
+
+Example:
+
+```
+nerdctl container healthcheck <container-id>
+```
+
+### Future Work (WIP)
+
+Since nerdctl is daemonless and does not have a persistent background process, we rely on systemd(or external schedulers)
+to invoke nerdctl container healthcheck at configured intervals. This allows periodic health checks for containers in a
+systemd-based environment. We are actively working on automating health checks, where we will listen to container lifecycle
+events and generate appropriate systemd service and timer units. This will enable nerdctl to support automated,
+Docker-compatible health checks by leveraging systemd for scheduling and lifecycle integration.
\ No newline at end of file
diff --git a/pkg/api/types/container_types.go b/pkg/api/types/container_types.go
index 7489d449..7765c239 100644
--- a/pkg/api/types/container_types.go
+++ b/pkg/api/types/container_types.go
@@ -284,6 +284,15 @@ type ContainerCreateOptions struct {
 	// ImagePullOpt specifies image pull options which holds the ImageVerifyOptions for verifying the image.
 	ImagePullOpt ImagePullOptions
 
+	// Healthcheck related fields
+	HealthCmd           string
+	HealthInterval      time.Duration
+	HealthTimeout       time.Duration
+	HealthRetries       int
+	HealthStartPeriod   time.Duration
+	HealthStartInterval time.Duration
+	NoHealthcheck       bool
+
 	// UserNS name for user namespace mapping of container
 	UserNS string
 }
diff --git a/pkg/cmd/container/create.go b/pkg/cmd/container/create.go
index 59270de8..76b0ee24 100644
--- a/pkg/cmd/container/create.go
+++ b/pkg/cmd/container/create.go
@@ -25,6 +25,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"reflect"
 	"runtime"
 	"strconv"
 	"strings"
@@ -47,6 +48,7 @@ import (
 	"github.com/containerd/nerdctl/v2/pkg/containerutil"
 	"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore"
 	"github.com/containerd/nerdctl/v2/pkg/flagutil"
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
 	"github.com/containerd/nerdctl/v2/pkg/idgen"
 	"github.com/containerd/nerdctl/v2/pkg/imgutil"
 	"github.com/containerd/nerdctl/v2/pkg/imgutil/load"
@@ -333,6 +335,15 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
 	}
 	cOpts = append(cOpts, rtCOpts...)
 
+	// Generate health check config based on CLI flags and image.
+	healthcheckConfig, err := withHealthcheck(options, ensuredImage)
+	if err != nil {
+		return nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err
+	}
+	if healthcheckConfig != "" {
+		internalLabels.healthcheck = healthcheckConfig
+	}
+
 	lCOpts, err := withContainerLabels(options.Label, options.LabelFile, ensuredImage)
 	if err != nil {
 		return nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err
@@ -706,6 +717,8 @@ type internalLabels struct {
 	deviceMapping []dockercompat.DeviceMapping
 
 	user string
+
+	healthcheck string
 }
 
 // WithInternalLabels sets the internal labels for a container.
@@ -829,9 +842,69 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
 		m[labels.User] = internalLabels.user
 	}
 
+	if len(internalLabels.healthcheck) > 0 {
+		m[labels.HealthCheck] = internalLabels.healthcheck
+	}
+
 	return containerd.WithAdditionalContainerLabels(m), nil
 }
 
+func withHealthcheck(options types.ContainerCreateOptions, ensuredImage *imgutil.EnsuredImage) (string, error) {
+	// If explicitly disabled
+	if options.NoHealthcheck {
+		hc := &healthcheck.Healthcheck{
+			Test: []string{"NONE"},
+		}
+		hcJSON, err := hc.ToJSONString()
+		if err != nil {
+			return "", fmt.Errorf("failed to serialize disabled healthcheck config: %w", err)
+		}
+		return hcJSON, nil
+	}
+
+	// Start with health checks in image if present
+	hc := &healthcheck.Healthcheck{}
+	if ensuredImage != nil && ensuredImage.ImageConfig.Labels != nil {
+		if label := ensuredImage.ImageConfig.Labels[labels.HealthCheck]; label != "" {
+			parsed, err := healthcheck.HealthCheckFromJSON(label)
+			if err != nil {
+				return "", fmt.Errorf("failed to parse healthcheck label in image: %w", err)
+			}
+			hc = parsed
+		}
+	}
+
+	// Apply CLI overrides
+	if options.HealthCmd != "" {
+		hc.Test = []string{"CMD-SHELL", options.HealthCmd}
+	}
+	if options.HealthInterval != 0 {
+		hc.Interval = options.HealthInterval
+	}
+	if options.HealthTimeout != 0 {
+		hc.Timeout = options.HealthTimeout
+	}
+	if options.HealthRetries != 0 {
+		hc.Retries = options.HealthRetries
+	}
+	if options.HealthStartPeriod != 0 {
+		hc.StartPeriod = options.HealthStartPeriod
+	}
+	if options.HealthStartInterval != 0 {
+		hc.StartInterval = options.HealthStartInterval
+	}
+
+	// If no healthcheck config is set (via CLI or image), return empty string so we skip adding to container config.
+	if reflect.DeepEqual(hc, &healthcheck.Healthcheck{}) {
+		return "", nil
+	}
+	hcJSON, err := hc.ToJSONString()
+	if err != nil {
+		return "", fmt.Errorf("failed to serialize healthcheck config: %w", err)
+	}
+	return hcJSON, nil
+}
+
 // loadNetOpts loads network options into InternalLabels.
 func (il *internalLabels) loadNetOpts(opts types.NetworkOptions) {
 	il.hostname = opts.Hostname
diff --git a/pkg/cmd/container/health_check.go b/pkg/cmd/container/health_check.go
new file mode 100644
index 00000000..6fe31c8e
--- /dev/null
+++ b/pkg/cmd/container/health_check.go
@@ -0,0 +1,96 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package container
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	containerd "github.com/containerd/containerd/v2/client"
+
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
+	"github.com/containerd/nerdctl/v2/pkg/labels"
+)
+
+// HealthCheck executes the health check command for a container
+func HealthCheck(ctx context.Context, client *containerd.Client, container containerd.Container) error {
+	// verify container status and get task
+	task, err := isContainerRunning(ctx, container)
+	if err != nil {
+		return err
+	}
+
+	// Check if container has health check configured
+	info, err := container.Info(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to get container info: %w", err)
+	}
+	hcConfigJSON, ok := info.Labels[labels.HealthCheck]
+	if !ok {
+		return fmt.Errorf("container has no health check configured")
+	}
+
+	// Parse health check configuration from labels
+	var hcConfig *healthcheck.Healthcheck
+	hcConfig, err = healthcheck.HealthCheckFromJSON(hcConfigJSON)
+	if err != nil {
+		return fmt.Errorf("invalid health check configuration: %w", err)
+	}
+	if hcConfig.Test == nil {
+		return fmt.Errorf("health check configuration has no test")
+	}
+
+	// Populate defaults
+	hcConfig.Interval = timeoutWithDefault(hcConfig.Interval, healthcheck.DefaultProbeInterval)
+	hcConfig.Timeout = timeoutWithDefault(hcConfig.Timeout, healthcheck.DefaultProbeTimeout)
+	hcConfig.StartPeriod = timeoutWithDefault(hcConfig.StartPeriod, healthcheck.DefaultStartPeriod)
+	hcConfig.StartInterval = timeoutWithDefault(hcConfig.StartInterval, healthcheck.DefaultStartInterval)
+	if hcConfig.Retries == 0 {
+		hcConfig.Retries = healthcheck.DefaultProbeRetries
+	}
+
+	// Execute the health check
+	return healthcheck.ExecuteHealthCheck(ctx, task, container, hcConfig)
+}
+
+func isContainerRunning(ctx context.Context, container containerd.Container) (containerd.Task, error) {
+	// Get container task to check status
+	task, err := container.Task(ctx, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get container task: %w", err)
+	}
+
+	// Check if container is running
+	status, err := task.Status(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get container status: %w", err)
+	}
+	if status.Status != containerd.Running {
+		return nil, fmt.Errorf("container is not running (status: %s)", status.Status)
+	}
+
+	return task, nil
+}
+
+// If configuredValue is zero, use defaultValue instead.
+func timeoutWithDefault(configuredValue time.Duration, defaultValue time.Duration) time.Duration {
+	if configuredValue == 0 {
+		return defaultValue
+	}
+	return configuredValue
+}
diff --git a/pkg/cmd/container/list_util.go b/pkg/cmd/container/list_util.go
index da63106e..a2fdb0a2 100644
--- a/pkg/cmd/container/list_util.go
+++ b/pkg/cmd/container/list_util.go
@@ -19,6 +19,7 @@ package container
 import (
 	"context"
 	"fmt"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -164,11 +165,15 @@ func (cl *containerFilterContext) foldIDFilter(_ context.Context, filter, value
 }
 
 func (cl *containerFilterContext) foldNameFilter(_ context.Context, filter, value string) error {
+	re, err := regexp.Compile(value)
+	if err != nil {
+		return err
+	}
 	cl.nameFilterFuncs = append(cl.nameFilterFuncs, func(name string) bool {
 		if value == "" {
 			return true
 		}
-		return strings.Contains(name, value)
+		return re.MatchString(name)
 	})
 	return nil
 }
diff --git a/pkg/cmd/network/list.go b/pkg/cmd/network/list.go
index 731c51b1..d27333a3 100644
--- a/pkg/cmd/network/list.go
+++ b/pkg/cmd/network/list.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"regexp"
 	"strings"
 	"text/tabwriter"
 	"text/template"
@@ -147,21 +148,21 @@ func getNetworkFilterFuncs(filters []string) ([]func(*map[string]string) bool, [
 
 	for _, filter := range filters {
 		if strings.HasPrefix(filter, "name") || strings.HasPrefix(filter, "label") {
-			subs := strings.SplitN(filter, "=", 2)
-			if len(subs) < 2 {
+			filter, value, ok := strings.Cut(filter, "=")
+			if !ok {
 				continue
 			}
-			switch subs[0] {
+			switch filter {
 			case "name":
+				re, err := regexp.Compile(value)
+				if err != nil {
+					return nil, nil, err
+				}
 				nameFilterFuncs = append(nameFilterFuncs, func(name string) bool {
-					return strings.Contains(name, subs[1])
+					return re.MatchString(name)
 				})
 			case "label":
-				v, k, hasValue := "", subs[1], false
-				if subs := strings.SplitN(subs[1], "=", 2); len(subs) == 2 {
-					hasValue = true
-					k, v = subs[0], subs[1]
-				}
+				k, v, hasValue := strings.Cut(value, "=")
 				labelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool {
 					if labels == nil {
 						return false
diff --git a/pkg/cmd/volume/list.go b/pkg/cmd/volume/list.go
index bb0654ba..126b65b9 100644
--- a/pkg/cmd/volume/list.go
+++ b/pkg/cmd/volume/list.go
@@ -20,6 +20,7 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
+	"regexp"
 	"strconv"
 	"strings"
 	"text/tabwriter"
@@ -216,21 +217,21 @@ func getVolumeFilterFuncs(filters []string) ([]func(*map[string]string) bool, []
 	}
 	for _, filter := range filters {
 		if strings.HasPrefix(filter, "name") || strings.HasPrefix(filter, "label") {
-			subs := strings.SplitN(filter, "=", 2)
-			if len(subs) < 2 {
+			filter, value, ok := strings.Cut(filter, "=")
+			if !ok {
 				continue
 			}
-			switch subs[0] {
+			switch filter {
 			case "name":
+				re, err := regexp.Compile(value)
+				if err != nil {
+					return nil, nil, nil, false, err
+				}
 				nameFilterFuncs = append(nameFilterFuncs, func(name string) bool {
-					return strings.Contains(name, subs[1])
+					return re.MatchString(name)
 				})
 			case "label":
-				v, k, hasValue := "", subs[1], false
-				if subs := strings.SplitN(subs[1], "=", 2); len(subs) == 2 {
-					hasValue = true
-					k, v = subs[0], subs[1]
-				}
+				k, v, hasValue := strings.Cut(value, "=")
 				labelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool {
 					if labels == nil {
 						return false
diff --git a/pkg/healthcheck/executor.go b/pkg/healthcheck/executor.go
new file mode 100644
index 00000000..1b3f16a7
--- /dev/null
+++ b/pkg/healthcheck/executor.go
@@ -0,0 +1,200 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package healthcheck
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"syscall"
+	"time"
+
+	"github.com/opencontainers/runtime-spec/specs-go"
+
+	containerd "github.com/containerd/containerd/v2/client"
+	"github.com/containerd/containerd/v2/pkg/cio"
+	"github.com/containerd/log"
+
+	"github.com/containerd/nerdctl/v2/pkg/idgen"
+)
+
+// ExecuteHealthCheck executes the health check command for a container
+func ExecuteHealthCheck(ctx context.Context, task containerd.Task, container containerd.Container, hc *Healthcheck) error {
+	// Prepare process spec for health check command
+	processSpec, err := prepareProcessSpec(ctx, container, hc)
+	if err != nil {
+		return err
+	}
+	if processSpec == nil {
+		return nil
+	}
+
+	startTime := time.Now()
+	result, err := probeHealthCheck(ctx, task, hc, processSpec)
+	if err != nil {
+		_ = updateHealthStatus(ctx, container, hc, &HealthcheckResult{
+			Start:    startTime,
+			End:      time.Now(),
+			ExitCode: -1,
+			Output:   err.Error(),
+		})
+		return fmt.Errorf("health check probe failed: %w", err)
+	}
+
+	// Success case, update health status
+	result.Start = startTime
+	if err := updateHealthStatus(ctx, container, hc, result); err != nil {
+		return fmt.Errorf("failed to update health status: %w", err)
+	}
+	return nil
+}
+
+// probeHealthCheck executes the health check command inside the container context
+func probeHealthCheck(ctx context.Context, task containerd.Task, hc *Healthcheck, processSpec *specs.Process) (*HealthcheckResult, error) {
+	execID := "health-check-" + idgen.TruncateID(idgen.GenerateID())
+	outputBuf := NewResizableBuffer(MaxOutputLen)
+
+	process, err := task.Exec(ctx, execID, processSpec, cio.NewCreator(
+		cio.WithStreams(nil, outputBuf, outputBuf),
+	))
+	if err != nil {
+		log.G(ctx).Debugf("failed to exec health check: %v", err)
+		return nil, fmt.Errorf("exec error: %w", err)
+	}
+
+	if err := process.Start(ctx); err != nil {
+		log.G(ctx).Debugf("failed to start health check: %v", err)
+		return nil, fmt.Errorf("start error: %w", err)
+	}
+
+	exitStatusC, err := process.Wait(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to wait for health check: %w", err)
+	}
+
+	select {
+	case <-time.After(hc.Timeout):
+		_ = process.Kill(ctx, syscall.SIGKILL)
+		go func() { <-exitStatusC }()
+
+		msg := fmt.Sprintf("Health check exceeded timeout (%v)", hc.Timeout)
+		if out := outputBuf.String(); len(out) > 0 {
+			msg = fmt.Sprintf("Health check exceeded timeout (%v): %s", hc.Timeout, out)
+		}
+
+		log.G(ctx).Debugf("health check timed out: %s", msg)
+
+		return &HealthcheckResult{
+			ExitCode: -1,
+			Output:   msg,
+			End:      time.Now(),
+		}, nil
+
+	case exitStatus := <-exitStatusC:
+		code, _, _ := exitStatus.Result()
+		return &HealthcheckResult{
+			ExitCode: int(code),
+			Output:   outputBuf.String(),
+			End:      time.Now(),
+		}, nil
+	}
+}
+
+// updateHealthStatus updates the health status based on the health check result
+func updateHealthStatus(ctx context.Context, container containerd.Container, hcConfig *Healthcheck, hcResult *HealthcheckResult) error {
+	// Get current health state from labels
+	currentHealth, err := readHealthStateFromLabels(ctx, container)
+	if err != nil {
+		return fmt.Errorf("failed to read health state from labels: %w", err)
+	}
+	if currentHealth == nil {
+		currentHealth = &HealthState{
+			Status:        Starting,
+			FailingStreak: 0,
+		}
+	}
+
+	// Check if still within start period
+	startPeriod := hcConfig.StartPeriod
+	info, err := container.Info(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to get container info: %w", err)
+	}
+	containerCreated := info.CreatedAt
+	stillInStartPeriod := hcResult.Start.Sub(containerCreated) < startPeriod
+
+	// Update health status based on exit code
+	if hcResult.ExitCode == 0 {
+		currentHealth.Status = Healthy
+		currentHealth.FailingStreak = 0
+	} else if !stillInStartPeriod {
+		currentHealth.FailingStreak++
+		if currentHealth.FailingStreak >= hcConfig.Retries {
+			currentHealth.Status = Unhealthy
+		}
+	}
+
+	// Write updated health state back to labels
+	if err := writeHealthStateToLabels(ctx, container, currentHealth); err != nil {
+		return fmt.Errorf("failed to write health state to labels: %w", err)
+	}
+
+	// Store the latest health check result in the log file
+	if err := writeHealthLog(ctx, container, hcResult); err != nil {
+		return fmt.Errorf("failed to write health log: %w", err)
+	}
+	return nil
+}
+
+// prepareProcessSpec prepares the process spec for health check execution
+func prepareProcessSpec(ctx context.Context, container containerd.Container, hcConfig *Healthcheck) (*specs.Process, error) {
+	hcCommand := hcConfig.Test
+
+	var args []string
+	switch hcCommand[0] {
+	case TestNone, CmdNone:
+		log.G(ctx).Debug("health check is set to NONE, skipping execution")
+		return nil, nil
+	case Cmd:
+		args = hcCommand[1:]
+	case CmdShell:
+		if len(hcCommand) < 2 || strings.TrimSpace(hcCommand[1]) == "" {
+			return nil, fmt.Errorf("no health check command specified")
+		}
+		args = []string{"/bin/sh", "-c", strings.Join(hcCommand[1:], " ")}
+	default:
+		args = hcCommand
+	}
+
+	if len(args) < 1 || args[0] == "" {
+		return nil, fmt.Errorf("no health check command specified")
+	}
+
+	// Get container spec for environment and working directory
+	spec, err := container.Spec(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get container spec: %w", err)
+	}
+	processSpec := &specs.Process{
+		Args: args,
+		Env:  spec.Process.Env,
+		User: spec.Process.User,
+		Cwd:  spec.Process.Cwd,
+	}
+
+	return processSpec, nil
+}
diff --git a/pkg/healthcheck/health.go b/pkg/healthcheck/health.go
new file mode 100644
index 00000000..8e0301b4
--- /dev/null
+++ b/pkg/healthcheck/health.go
@@ -0,0 +1,139 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package healthcheck
+
+import (
+	"encoding/json"
+	"time"
+)
+
+type HealthStatus = string
+
+// Health states
+const (
+	NoHealthcheck HealthStatus = "none" // Indicates there is no healthcheck
+	Starting      HealthStatus = "starting"
+	Healthy       HealthStatus = "healthy"
+	Unhealthy     HealthStatus = "unhealthy"
+)
+
+// Healthcheck cmd types
+const (
+	CmdNone  = "NONE"
+	Cmd      = "CMD"
+	CmdShell = "CMD-SHELL"
+	TestNone = ""
+)
+
+const (
+	DefaultProbeInterval   = 30 * time.Second // Default interval between probe runs. Also applies before the first probe.
+	DefaultProbeTimeout    = 30 * time.Second // Max duration a single probe run may take before it's considered failed.
+	DefaultStartPeriod     = 0 * time.Second  // Grace period for container startup before health checks count as failures.
+	DefaultStartInterval   = 5 * time.Second  // Interval between checks during the start period.
+	DefaultProbeRetries    = 3                // Number of consecutive failures before marking container as unhealthy.
+	MaxLogEntries          = 5                // Maximum number of health check log entries to keep.
+	MaxOutputLenForInspect = 4096             // Max output length (in bytes) stored in health check logs during inspect. Longer outputs are truncated.
+	MaxOutputLen           = 1 * 1024 * 1024  // Max output size for health check logs: 1MB limit (prevents excessive memory usage)
+	HealthLogFilename      = "health.json"    // HealthLogFilename is the name of the file used to persist health check status for a container.
+)
+
+// NOTE: Health, HealthcheckResult and Healthcheck types are kept Docker-compatible.
+// See: https://github.com/moby/moby/blob/9d1b069a4bfdcee368e67767978eff596b696d4c/api/types/container/health.go
+// Health stores information about the container's healthcheck results
+type Health struct {
+	Status        HealthStatus         // Status is one of [Starting], [Healthy] or [Unhealthy].
+	FailingStreak int                  // FailingStreak is the number of consecutive failures
+	Log           []*HealthcheckResult // Log contains the last few results (oldest first)
+}
+
+// HealthcheckResult stores information about a single run of a healthcheck probe
+type HealthcheckResult struct {
+	Start    time.Time // Start is the time this check started
+	End      time.Time // End is the time this check ended
+	ExitCode int       // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe
+	Output   string    // Output from last check
+}
+
+// Healthcheck represents the health check configuration
+type Healthcheck struct {
+	Test          []string      `json:"Test,omitempty"`          // Test is the check to perform that the container is healthy
+	Interval      time.Duration `json:"Interval,omitempty"`      // Interval is the time to wait between checks
+	Timeout       time.Duration `json:"Timeout,omitempty"`       // Timeout is the time to wait before considering the check to have hung
+	Retries       int           `json:"Retries,omitempty"`       // Retries is the number of consecutive failures needed to consider a container as unhealthy
+	StartPeriod   time.Duration `json:"StartPeriod,omitempty"`   // StartPeriod is the period for the container to initialize before the health check starts
+	StartInterval time.Duration `json:"StartInterval,omitempty"` // StartInterval is the time between health checks during the start period
+}
+
+// HealthState stores the current health state of a container
+type HealthState struct {
+	Status        HealthStatus // Status is one of [Starting], [Healthy] or [Unhealthy]
+	FailingStreak int          // FailingStreak is the number of consecutive failures
+}
+
+// ToJSONString serializes HealthState to a JSON string for label storage
+func (hs *HealthState) ToJSONString() (string, error) {
+	b, err := json.Marshal(hs)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// HealthStateFromJSON deserializes a JSON string into a HealthState
+func HealthStateFromJSON(s string) (*HealthState, error) {
+	var hs HealthState
+	if err := json.Unmarshal([]byte(s), &hs); err != nil {
+		return nil, err
+	}
+	return &hs, nil
+}
+
+// ToJSONString serializes a Healthcheck struct to a JSON string
+func (hc *Healthcheck) ToJSONString() (string, error) {
+	b, err := json.Marshal(hc)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// HealthCheckFromJSON deserializes a JSON string into a Healthcheck struct
+func HealthCheckFromJSON(s string) (*Healthcheck, error) {
+	var hc Healthcheck
+	if err := json.Unmarshal([]byte(s), &hc); err != nil {
+		return nil, err
+	}
+	return &hc, nil
+}
+
+// ToJSONString serializes a HealthcheckResult struct to a JSON string
+func (r *HealthcheckResult) ToJSONString() (string, error) {
+	b, err := json.Marshal(r)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// HealthcheckResultFromJSON deserializes a JSON string into a HealthcheckResult struct
+func HealthcheckResultFromJSON(s string) (*HealthcheckResult, error) {
+	var r HealthcheckResult
+	if err := json.Unmarshal([]byte(s), &r); err != nil {
+		return nil, err
+	}
+	return &r, nil
+}
diff --git a/pkg/healthcheck/log.go b/pkg/healthcheck/log.go
new file mode 100644
index 00000000..1664b502
--- /dev/null
+++ b/pkg/healthcheck/log.go
@@ -0,0 +1,255 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package healthcheck
+
+import (
+	"bufio"
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	containerd "github.com/containerd/containerd/v2/client"
+	"github.com/containerd/log"
+
+	"github.com/containerd/nerdctl/v2/pkg/internal/filesystem"
+	"github.com/containerd/nerdctl/v2/pkg/labels"
+)
+
+// writeHealthLog writes the latest health check result to the log file, appending it to existing logs.
+func writeHealthLog(ctx context.Context, container containerd.Container, result *HealthcheckResult) error {
+	stateDir, err := getContainerStateDir(ctx, container)
+	if err != nil {
+		return fmt.Errorf("error fetching container state dir: %v", err)
+	}
+
+	data, err := result.ToJSONString()
+	if err != nil {
+		return fmt.Errorf("failed to marshal health log: %w", err)
+	}
+
+	// Ensure file exists before writing
+	if err := ensureHealthLogFile(stateDir); err != nil {
+		return err
+	}
+
+	// Write the latest result to the file
+	logPath := filepath.Join(stateDir, HealthLogFilename)
+	return filesystem.WithAppendLock(logPath, func(file *os.File) error {
+		if _, err := file.Seek(0, io.SeekEnd); err != nil {
+			return fmt.Errorf("seek error: %w", err)
+		}
+		if _, err := file.Write(append([]byte(data), '\n')); err != nil {
+			return fmt.Errorf("failed to write health log: %w", err)
+		}
+		return nil
+	})
+}
+
+// ReadHealthStatusForInspect reads the health state from labels and the last MaxLogEntries health check result logs.
+func ReadHealthStatusForInspect(stateDir, healthState string) (*Health, error) {
+	state, err := HealthStateFromJSON(healthState)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse health state: %w", err)
+	}
+
+	logPath := filepath.Join(stateDir, HealthLogFilename)
+	var logs []*HealthcheckResult
+	err = filesystem.WithReadOnlyLock(logPath, func() error {
+		file, err := os.Open(logPath)
+		if err != nil {
+			if os.IsNotExist(err) {
+				return nil
+			}
+			return err
+		}
+		defer file.Close()
+
+		reader := bufio.NewReader(file)
+		for {
+			line, err := reader.ReadString('\n')
+			if err != nil {
+				if errors.Is(err, io.EOF) {
+					break
+				}
+				return err
+			}
+
+			line = strings.TrimRight(line, "\n")
+			result, err := HealthcheckResultFromJSON(line)
+			if err != nil {
+				log.L.Warnf("failed to parse healthcheck log line: %v", err)
+				continue
+			}
+			logs = append(logs, result)
+		}
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	// Keep only the last MaxLogEntries
+	n := len(logs)
+	if n > MaxLogEntries {
+		logs = logs[n-MaxLogEntries:]
+	}
+
+	// Reverse for newest-first order
+	for i, j := 0, len(logs)-1; i < j; i, j = i+1, j-1 {
+		logs[i], logs[j] = logs[j], logs[i]
+	}
+
+	// Truncate log outputs to avoid flooding inspect output
+	for _, logEntry := range logs {
+		if len(logEntry.Output) > MaxOutputLenForInspect {
+			buf := NewResizableBuffer(MaxOutputLenForInspect)
+			_, _ = buf.Write([]byte(logEntry.Output))
+			logEntry.Output = buf.String()
+		}
+	}
+
+	// Create a Health object with the health state and logs
+	health := &Health{
+		Status:        state.Status,
+		FailingStreak: state.FailingStreak,
+		Log:           logs,
+	}
+
+	return health, nil
+}
+
+// writeHealthStateToLabels writes the health state to container labels
+func writeHealthStateToLabels(ctx context.Context, container containerd.Container, healthState *HealthState) error {
+	hs, err := healthState.ToJSONString()
+	if err != nil {
+		return fmt.Errorf("failed to marshal health healthState: %w", err)
+	}
+
+	lbs, err := container.Labels(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to get container labels: %w", err)
+	}
+
+	// Update healthState label
+	lbs[labels.HealthState] = hs
+	_, err = container.SetLabels(ctx, lbs)
+	if err != nil {
+		return fmt.Errorf("failed to update container labels: %w", err)
+	}
+
+	return nil
+}
+
+// readHealthStateFromLabels reads the health state from container labels
+func readHealthStateFromLabels(ctx context.Context, container containerd.Container) (*HealthState, error) {
+	lbs, err := container.Labels(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get container labels: %w", err)
+	}
+
+	// Check if health state label exists
+	stateJSON, ok := lbs[labels.HealthState]
+	if !ok {
+		return nil, nil
+	}
+
+	// HealthCheckFromJSON health state from JSON
+	state, err := HealthStateFromJSON(stateJSON)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse health state: %w", err)
+	}
+
+	return state, nil
+}
+
+// ensureHealthLogFile creates the health.json file if it doesn't exist.
+func ensureHealthLogFile(stateDir string) error {
+	healthLogPath := filepath.Join(stateDir, HealthLogFilename)
+
+	// Ensure container state directory exists
+	if _, err := os.Stat(stateDir); os.IsNotExist(err) {
+		return fmt.Errorf("container state directory does not exist: %s", stateDir)
+	}
+
+	// Create health.json if it doesn't exist
+	if _, err := os.Stat(healthLogPath); os.IsNotExist(err) {
+		return filesystem.WriteFile(healthLogPath, []byte{}, 0600)
+	}
+
+	return nil
+}
+
+// getContainerStateDir returns the container's state directory from labels.
+func getContainerStateDir(ctx context.Context, container containerd.Container) (string, error) {
+	info, err := container.Info(ctx)
+	if err != nil {
+		return "", err
+	}
+	stateDir, ok := info.Labels[labels.StateDir]
+	if !ok {
+		return "", err
+	}
+	return stateDir, nil
+}
+
+// ResizableBuffer collects output with a configurable upper limit.
+type ResizableBuffer struct {
+	mu        sync.Mutex
+	buf       bytes.Buffer
+	maxSize   int
+	truncated bool
+}
+
+// NewResizableBuffer returns a new buffer with the given size limit in bytes.
+func NewResizableBuffer(maxSize int) *ResizableBuffer {
+	return &ResizableBuffer{maxSize: maxSize}
+}
+
+func (b *ResizableBuffer) Write(p []byte) (int, error) {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	remaining := b.maxSize - b.buf.Len()
+	if remaining <= 0 {
+		b.truncated = true
+		return len(p), nil
+	}
+
+	if len(p) > remaining {
+		b.truncated = true
+		p = p[:remaining]
+	}
+
+	return b.buf.Write(p)
+}
+
+func (b *ResizableBuffer) String() string {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	s := b.buf.String()
+	if b.truncated {
+		s += "... [truncated]"
+	}
+	return s
+}
diff --git a/pkg/imgutil/imgutil.go b/pkg/imgutil/imgutil.go
index 3f8076df..08d10be6 100644
--- a/pkg/imgutil/imgutil.go
+++ b/pkg/imgutil/imgutil.go
@@ -40,9 +40,11 @@ import (
 
 	"github.com/containerd/nerdctl/v2/pkg/api/types"
 	"github.com/containerd/nerdctl/v2/pkg/errutil"
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
 	"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
 	"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver"
 	"github.com/containerd/nerdctl/v2/pkg/imgutil/pull"
+	"github.com/containerd/nerdctl/v2/pkg/labels"
 	"github.com/containerd/nerdctl/v2/pkg/referenceutil"
 )
 
@@ -271,6 +273,10 @@ func getImageConfig(ctx context.Context, image containerd.Image) (*ocispec.Image
 		if err := json.Unmarshal(b, &ocispecImage); err != nil {
 			return nil, err
 		}
+
+		if err := addHealthCheckToImageConfig(b, &ocispecImage.Config); err != nil {
+			log.G(ctx).WithError(err).Debug("failed to add health check config")
+		}
 		return &ocispecImage.Config, nil
 	default:
 		return nil, fmt.Errorf("unknown media type %q", desc.MediaType)
@@ -354,6 +360,9 @@ func ReadImageConfig(ctx context.Context, img containerd.Image) (ocispec.Image,
 	if err := json.Unmarshal(p, &config); err != nil {
 		return config, configDesc, err
 	}
+	if err := addHealthCheckToImageConfig(p, &config.Config); err != nil {
+		log.G(ctx).WithError(err).Debug("failed to add health check config")
+	}
 	return config, configDesc, nil
 }
 
@@ -464,3 +473,28 @@ func GetDanglingImages(ctx context.Context, client *containerd.Client, filters .
 
 	return ApplyFilters(allImages, filters...)
 }
+
+// addHealthCheckToImageConfig extracts health check information from the image content store and adds it to the labels
+func addHealthCheckToImageConfig(rawConfigContent []byte, config *ocispec.ImageConfig) error {
+	var imgConfig struct {
+		Config struct {
+			Healthcheck *healthcheck.Healthcheck `json:"Healthcheck,omitempty"`
+		} `json:"config"`
+	}
+
+	if err := json.Unmarshal(rawConfigContent, &imgConfig); err != nil {
+		return err
+	}
+
+	if imgConfig.Config.Healthcheck != nil {
+		healthCheckJSON, err := json.Marshal(imgConfig.Config.Healthcheck)
+		if err != nil {
+			return err
+		}
+		if config.Labels == nil {
+			config.Labels = make(map[string]string)
+		}
+		config.Labels[labels.HealthCheck] = string(healthCheckJSON)
+	}
+	return nil
+}
diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go
index 43321456..fbcf57d0 100644
--- a/pkg/inspecttypes/dockercompat/dockercompat.go
+++ b/pkg/inspecttypes/dockercompat/dockercompat.go
@@ -44,6 +44,7 @@ import (
 	"github.com/containerd/go-cni"
 	"github.com/containerd/log"
 
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
 	"github.com/containerd/nerdctl/v2/pkg/imgutil"
 	"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native"
 	"github.com/containerd/nerdctl/v2/pkg/ipcutil"
@@ -210,9 +211,9 @@ type Config struct {
 	// TODO: Tty          bool        // Attach standard streams to a tty, including stdin if it is not closed.
 	// TODO: OpenStdin    bool        // Open stdin
 	// TODO: StdinOnce    bool        // If true, close stdin after the 1 attached client disconnects.
-	Env []string `json:",omitempty"` // List of environment variable to set in the container
-	Cmd []string `json:",omitempty"` // Command to run when starting the container
-	// TODO Healthcheck     *HealthConfig       `json:",omitempty"` // Healthcheck describes how to check the container is healthy
+	Env         []string                 `json:",omitempty"` // List of environment variable to set in the container
+	Cmd         []string                 `json:",omitempty"` // Command to run when starting the container
+	Healthcheck *healthcheck.Healthcheck `json:",omitempty"` // Healthcheck describes how to check the container is healthy
 	// TODO: ArgsEscaped     bool                `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific).
 	// TODO: Image           string              // Name of the image as it was passed by the operator (e.g. could be symbolic)
 	Volumes    map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container
@@ -240,7 +241,7 @@ type ContainerState struct {
 	Error      string
 	StartedAt  string
 	FinishedAt string
-	// TODO: Health     *Health `json:",omitempty"`
+	Health     *healthcheck.Health `json:",omitempty"`
 }
 
 type NetworkSettings struct {
@@ -580,6 +581,26 @@ func ContainerFromNative(n *native.Container) (*Container, error) {
 		c.Config.User = n.Labels[labels.User]
 	}
 
+	// Add health check config if present in labels
+	if hConfig, ok := n.Labels[labels.HealthCheck]; ok && hConfig != "" {
+		healthCheckConfig, err := healthcheck.HealthCheckFromJSON(hConfig)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse healthcheck label: %w", err)
+		}
+		c.Config.Healthcheck = healthCheckConfig
+	}
+
+	// Add health status to container state.
+	if healthState, ok := n.Labels[labels.HealthState]; ok && healthState != "" {
+		healthStatus, err := healthcheck.ReadHealthStatusForInspect(n.Labels[labels.StateDir], n.Labels[labels.HealthState])
+		if err != nil {
+			return nil, fmt.Errorf("failed to get health status for inspect: %w", err)
+		}
+		if healthStatus != nil {
+			c.State.Health = healthStatus
+		}
+	}
+
 	return c, nil
 }
 
@@ -629,6 +650,15 @@ func ImageFromNative(nativeImage *native.Image) (*Image, error) {
 		ExposedPorts: portSet,
 	}
 
+	// Add health check if present in labels
+	if healthStr, ok := imgOCI.Config.Labels[labels.HealthCheck]; ok && healthStr != "" {
+		healthCheckConfig, err := healthcheck.HealthCheckFromJSON(healthStr)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse healthcheck label: %w", err)
+		}
+		image.Config.Healthcheck = healthCheckConfig
+	}
+
 	return image, nil
 }
 
diff --git a/pkg/internal/filesystem/lock.go b/pkg/internal/filesystem/lock.go
index 847e4038..c278bbe9 100644
--- a/pkg/internal/filesystem/lock.go
+++ b/pkg/internal/filesystem/lock.go
@@ -37,7 +37,7 @@ import (
 // If Lock returns nil, no other process will be able to place a read or write lock on the file until
 // this process exits, closes f, or calls Unlock on it.
 func Lock(path string) (file *os.File, err error) {
-	return commonlock(path, writeLock)
+	return commonlock(path, writeLock, false)
 }
 
 // ReadOnlyLock places an advisory read lock on the file, blocking until it can be locked.
@@ -45,10 +45,10 @@ func Lock(path string) (file *os.File, err error) {
 // If ReadOnlyLock returns nil, no other process will be able to place a write lock on
 // the file until this process exits, closes f, or calls Unlock on it.
 func ReadOnlyLock(path string) (file *os.File, err error) {
-	return commonlock(path, readLock)
+	return commonlock(path, readLock, false)
 }
 
-func commonlock(path string, mode lockType) (file *os.File, err error) {
+func commonlock(path string, mode lockType, appendMode bool) (file *os.File, err error) {
 	defer func() {
 		if err != nil {
 			err = errors.Join(ErrLockFail, err, file.Close())
@@ -65,13 +65,21 @@ func commonlock(path string, mode lockType) (file *os.File, err error) {
 		}
 	}
 
-	file, err = os.Open(path)
-	if errors.Is(err, os.ErrNotExist) {
-		file, err = os.OpenFile(path, os.O_RDONLY|os.O_CREATE, privateFilePermission)
-	}
-
-	if err != nil {
-		return nil, err
+	// For append mode, open with specific flags
+	if appendMode {
+		file, err = os.OpenFile(path, os.O_WRONLY, privateFilePermission)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		// Preserve the original behavior for non-append operations
+		file, err = os.Open(path)
+		if errors.Is(err, os.ErrNotExist) {
+			file, err = os.OpenFile(path, os.O_RDONLY|os.O_CREATE, privateFilePermission)
+		}
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if err = platformSpecificLock(file, mode); err != nil {
@@ -123,3 +131,17 @@ func WithReadOnlyLock(path string, function func() error) (err error) {
 
 	return function()
 }
+
+// WithAppendLock executes the function with a file opened in append mode
+func WithAppendLock(path string, function func(file *os.File) error) (err error) {
+	file, err := commonlock(path, writeLock, true)
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		err = errors.Join(Unlock(file), err)
+	}()
+
+	return function(file)
+}
diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go
index 9356ad03..0c50324f 100644
--- a/pkg/labels/labels.go
+++ b/pkg/labels/labels.go
@@ -118,4 +118,10 @@ const (
 
 	// User is the username of the container
 	User = Prefix + "user"
+
+	// HealthCheck stores the health check configuration used to run health checks on the container
+	HealthCheck = Prefix + "healthcheck"
+
+	// HealthState stores the current health state (status and failing streak).
+	HealthState = Prefix + "healthstate"
 )



Test patch:
diff --git a/cmd/nerdctl/container/container_health_check_test.go b/cmd/nerdctl/container/container_health_check_test.go
new file mode 100644
index 00000000..66d60e30
--- /dev/null
+++ b/cmd/nerdctl/container/container_health_check_test.go
@@ -0,0 +1,556 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package container
+
+import (
+	"errors"
+	"strings"
+	"testing"
+	"time"
+
+	"gotest.tools/v3/assert"
+
+	"github.com/containerd/nerdctl/mod/tigron/expect"
+	"github.com/containerd/nerdctl/mod/tigron/require"
+	"github.com/containerd/nerdctl/mod/tigron/test"
+
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
+	"github.com/containerd/nerdctl/v2/pkg/testutil"
+	"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
+)
+
+func TestContainerHealthCheckBasic(t *testing.T) {
+	testCase := nerdtest.Setup()
+
+	// Docker CLI does not provide a standalone healthcheck command.
+	testCase.Require = require.Not(nerdtest.Docker)
+
+	testCase.SubTests = []*test.Case{
+		{
+			Description: "Container does not exist",
+			Command:     test.Command("container", "healthcheck", "non-existent"),
+			Expected:    test.Expects(1, []error{errors.New("no such container non-existent")}, nil),
+		},
+		{
+			Description: "Missing health check config",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: test.Expects(1, []error{errors.New("container has no health check configured")}, nil),
+		},
+		{
+			Description: "Basic health check success",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "echo healthy",
+					"--health-interval", "45s",
+					"--health-timeout", "30s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state to be present")
+						assert.Equal(t, healthcheck.Healthy, h.Status)
+						assert.Equal(t, 0, h.FailingStreak)
+						assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check on stopped container",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "echo healthy",
+					"--health-interval", "3s",
+					testutil.CommonImage, "sleep", "2")
+				helpers.Ensure("stop", data.Identifier())
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: test.Expects(1, []error{errors.New("container is not running (status: stopped)")}, nil),
+		},
+		{
+			Description: "Health check without task",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("create", "--name", data.Identifier(),
+					"--health-cmd", "echo healthy",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: test.Expects(1, []error{errors.New("failed to get container task: no running task found")}, nil),
+		},
+	}
+
+	testCase.Run(t)
+}
+
+func TestContainerHealthCheckAdvance(t *testing.T) {
+	testCase := nerdtest.Setup()
+
+	// Docker CLI does not provide a standalone healthcheck command.
+	testCase.Require = require.Not(nerdtest.Docker)
+
+	testCase.SubTests = []*test.Case{
+		{
+			Description: "Health check timeout scenario",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "sleep 10",
+					"--health-timeout", "2s",
+					"--health-interval", "1s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.FailingStreak, 1)
+						assert.Assert(t, len(inspect.State.Health.Log) > 0, "expected health log to have entries")
+						last := inspect.State.Health.Log[0]
+						assert.Equal(t, -1, last.ExitCode)
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check failing streak behavior",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "exit 1",
+					"--health-interval", "1s",
+					"--health-retries", "2",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				// Run healthcheck twice to ensure failing streak
+				for i := 0; i < 2; i++ {
+					helpers.Ensure("container", "healthcheck", data.Identifier())
+					time.Sleep(2 * time.Second)
+				}
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Unhealthy)
+						assert.Equal(t, h.FailingStreak, 2)
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check with start period",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "exit 1",
+					"--health-interval", "1s",
+					"--health-start-period", "5s",
+					"--health-retries", "2",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Starting)
+						assert.Equal(t, h.FailingStreak, 0)
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check with invalid command",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "not-a-real-cmd",
+					"--health-interval", "1s",
+					"--health-retries", "1",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Unhealthy)
+						assert.Equal(t, h.FailingStreak, 1)
+					}),
+				}
+			},
+		},
+		{
+			Description: "No healthcheck flag disables health status",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--no-healthcheck", testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						assert.Assert(t, inspect.State.Health == nil, "expected health to be nil with --no-healthcheck")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Healthcheck using CMD-SHELL format",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "echo shell-format", "--health-interval", "1s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(_, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy)
+						assert.Assert(t, len(h.Log) > 0)
+						assert.Assert(t, strings.Contains(h.Log[0].Output, "shell-format"))
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check uses container environment variables",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--env", "MYVAR=test-value",
+					"--health-cmd", "echo $MYVAR",
+					"--health-interval", "1s",
+					"--health-timeout", "1s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy)
+						assert.Assert(t, h.FailingStreak == 0)
+						assert.Assert(t, strings.Contains(h.Log[0].Output, "test"), "expected health log output to contain 'test'")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health check respects container WorkingDir",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--workdir", "/tmp",
+					"--health-cmd", "pwd",
+					"--health-interval", "1s",
+					"--health-timeout", "1s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy)
+						assert.Equal(t, h.FailingStreak, 0)
+						assert.Assert(t, strings.Contains(h.Log[0].Output, "/tmp"), "expected health log output to contain '/tmp'")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Healthcheck emits large output repeatedly",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "yes X | head -c 60000",
+					"--health-interval", "1s", "--health-timeout", "2s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				for i := 0; i < 3; i++ {
+					helpers.Ensure("container", "healthcheck", data.Identifier())
+					time.Sleep(2 * time.Second)
+				}
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(_, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy)
+						assert.Assert(t, len(h.Log) >= 3, "expected at least 3 health log entries")
+						for _, log := range h.Log {
+							assert.Assert(t, len(log.Output) >= 1024, "each output should be >= 1024 bytes")
+						}
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health log in inspect keeps only the latest 5 entries",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "exit 1",
+					"--health-interval", "1s",
+					"--health-retries", "1",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				for i := 0; i < 7; i++ {
+					helpers.Ensure("container", "healthcheck", data.Identifier())
+					time.Sleep(1 * time.Second)
+				}
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(_, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Unhealthy)
+						assert.Assert(t, len(h.Log) <= 5, "expected health log to contain at most 5 entries")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Healthcheck with large output gets truncated in health log",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "yes X | head -c 1048576", // 1MB output
+					"--health-interval", "1s", "--health-timeout", "2s",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("container", "healthcheck", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(_, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy)
+						assert.Equal(t, h.FailingStreak, 0)
+						assert.Assert(t, len(h.Log) == 1, "expected one log entry")
+						output := h.Log[0].Output
+						assert.Assert(t, strings.HasSuffix(output, "[truncated]"), "expected output to be truncated with '[truncated]'")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Health status transitions from healthy to unhealthy after retries",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				containerName := data.Identifier()
+				helpers.Ensure("run", "-d", "--name", containerName,
+					"--health-cmd", "exit 1",
+					"--health-timeout", "10s",
+					"--health-retries", "3",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				for i := 0; i < 4; i++ {
+					helpers.Ensure("container", "healthcheck", data.Identifier())
+					time.Sleep(2 * time.Second)
+				}
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Unhealthy)
+						assert.Assert(t, h.FailingStreak >= 3)
+					}),
+				}
+			},
+		},
+		{
+			Description: "Failed healthchecks in start-period do not change status",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "ls /foo || exit 1", "--health-retries", "2",
+					"--health-start-period", "30s", // long enough to stay in "starting"
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				// Run healthcheck 3 times (should still be in start period)
+				for i := 0; i < 3; i++ {
+					helpers.Ensure("container", "healthcheck", data.Identifier())
+					time.Sleep(1 * time.Second)
+				}
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Starting)
+						assert.Equal(t, h.FailingStreak, 0, "failing streak should not increase during start period")
+					}),
+				}
+			},
+		},
+		{
+			Description: "Successful healthcheck in start-period sets status to healthy",
+			Setup: func(data test.Data, helpers test.Helpers) {
+				helpers.Ensure("run", "-d", "--name", data.Identifier(),
+					"--health-cmd", "ls || exit 1", "--health-retries", "2",
+					testutil.CommonImage, "sleep", nerdtest.Infinity)
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", data.Identifier())
+			},
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				helpers.Ensure("container", "healthcheck", data.Identifier())
+				time.Sleep(1 * time.Second)
+				return helpers.Command("inspect", data.Identifier())
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					ExitCode: 0,
+					Output: expect.All(func(stdout, _ string, t *testing.T) {
+						inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+						h := inspect.State.Health
+						assert.Assert(t, h != nil, "expected health state")
+						assert.Equal(t, h.Status, healthcheck.Healthy, "expected healthy status even during start-period")
+						assert.Equal(t, h.FailingStreak, 0)
+					}),
+				}
+			},
+		},
+	}
+
+	testCase.Run(t)
+}
diff --git a/cmd/nerdctl/container/container_list_linux_test.go b/cmd/nerdctl/container/container_list_linux_test.go
index e7ce1c92..29687730 100644
--- a/cmd/nerdctl/container/container_list_linux_test.go
+++ b/cmd/nerdctl/container/container_list_linux_test.go
@@ -304,6 +304,42 @@ func TestContainerListWithFilter(t *testing.T) {
 		return nil
 	})
 
+	// should support regexp
+	base.Cmd("ps", "--filter", "name=.*"+testContainerA.name+".*").AssertOutWithFunc(func(stdout string) error {
+		lines := strings.Split(strings.TrimSpace(stdout), "\n")
+		if len(lines) < 2 {
+			return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
+		}
+
+		tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
+		err := tab.ParseHeader(lines[0])
+		if err != nil {
+			return fmt.Errorf("failed to parse header: %v", err)
+		}
+
+		containerName, _ := tab.ReadRow(lines[1], "NAMES")
+		assert.Equal(t, containerName, testContainerA.name)
+		return nil
+	})
+
+	// fully anchored regexp
+	base.Cmd("ps", "--filter", "name=^"+testContainerA.name+"$").AssertOutWithFunc(func(stdout string) error {
+		lines := strings.Split(strings.TrimSpace(stdout), "\n")
+		if len(lines) < 2 {
+			return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
+		}
+
+		tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
+		err := tab.ParseHeader(lines[0])
+		if err != nil {
+			return fmt.Errorf("failed to parse header: %v", err)
+		}
+
+		containerName, _ := tab.ReadRow(lines[1], "NAMES")
+		assert.Equal(t, containerName, testContainerA.name)
+		return nil
+	})
+
 	base.Cmd("ps", "-q", "--filter", "name="+testContainerA.name+testContainerA.name).AssertOutWithFunc(func(stdout string) error {
 		lines := strings.Split(strings.TrimSpace(stdout), "\n")
 		if len(lines) > 0 {
diff --git a/cmd/nerdctl/container/container_run_test.go b/cmd/nerdctl/container/container_run_test.go
index 345523f1..b621c852 100644
--- a/cmd/nerdctl/container/container_run_test.go
+++ b/cmd/nerdctl/container/container_run_test.go
@@ -837,3 +837,223 @@ func TestRunDomainname(t *testing.T) {
 		})
 	}
 }
+
+func TestRunHealthcheckFlags(t *testing.T) {
+	testCase := nerdtest.Setup()
+
+	testCases := []struct {
+		name              string
+		args              []string
+		shouldFail        bool
+		expectTest        []string
+		expectRetries     int
+		expectInterval    time.Duration
+		expectTimeout     time.Duration
+		expectStartPeriod time.Duration
+	}{
+		{
+			name: "Valid_full_config",
+			args: []string{
+				"--health-cmd", "curl -f http://localhost || exit 1",
+				"--health-interval", "30s",
+				"--health-timeout", "5s",
+				"--health-retries", "3",
+				"--health-start-period", "2s",
+			},
+			expectTest:        []string{"CMD-SHELL", "curl -f http://localhost || exit 1"},
+			expectInterval:    30 * time.Second,
+			expectTimeout:     5 * time.Second,
+			expectRetries:     3,
+			expectStartPeriod: 2 * time.Second,
+		},
+		{
+			name: "No_healthcheck",
+			args: []string{
+				"--no-healthcheck",
+			},
+			expectTest: []string{"NONE"},
+		},
+		{
+			name:       "No_healthcheck_flag",
+			args:       []string{},
+			expectTest: nil,
+		},
+		{
+			name: "Conflicting_flags",
+			args: []string{
+				"--no-healthcheck", "--health-cmd", "true",
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Negative_retries",
+			args: []string{
+				"--health-cmd", "true",
+				"--health-retries", "-2",
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Negative_timeout",
+			args: []string{
+				"--health-cmd", "true",
+				"--health-timeout", "-5s",
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Invalid_timeout_format",
+			args: []string{
+				"--health-cmd", "true",
+				"--health-timeout", "5blah",
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Health_cmd_cmd_shell",
+			args: []string{
+				"--health-cmd", "curl -f http://localhost || exit 1",
+			},
+			expectTest: []string{"CMD-SHELL", "curl -f http://localhost || exit 1"},
+		},
+		{
+			name: "Health_cmd_array_like",
+			args: []string{
+				"--health-cmd", "echo hello",
+			},
+			expectTest: []string{"CMD-SHELL", "echo hello"},
+		},
+		{
+			name: "Health_cmd_empty",
+			args: []string{
+				"--health-cmd", "",
+				"--health-retries", "2",
+			},
+			expectTest:    nil,
+			expectRetries: 2,
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+
+		testCase.SubTests = append(testCase.SubTests, &test.Case{
+			Description: tc.name,
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				args := append([]string{"run", "-d", "--name", tc.name}, tc.args...)
+				args = append(args, testutil.CommonImage, "sleep", "infinity")
+				return helpers.Command(args...)
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				if tc.shouldFail {
+					return &test.Expected{
+						ExitCode: expect.ExitCodeGenericFail,
+					}
+				}
+				return &test.Expected{
+					ExitCode: expect.ExitCodeSuccess,
+					Output: expect.All(
+						func(stdout, info string, t *testing.T) {
+							inspect := nerdtest.InspectContainer(helpers, tc.name)
+							hc := inspect.Config.Healthcheck
+							if tc.expectTest == nil {
+								assert.Assert(t, hc == nil || len(hc.Test) == 0)
+							} else {
+								assert.Assert(t, hc != nil)
+								assert.DeepEqual(t, hc.Test, tc.expectTest)
+							}
+							if tc.expectRetries > 0 {
+								assert.Equal(t, hc.Retries, tc.expectRetries)
+							}
+							if tc.expectTimeout > 0 {
+								assert.Equal(t, hc.Timeout, tc.expectTimeout)
+							}
+							if tc.expectInterval > 0 {
+								assert.Equal(t, hc.Interval, tc.expectInterval)
+							}
+							if tc.expectStartPeriod > 0 {
+								assert.Equal(t, hc.StartPeriod, tc.expectStartPeriod)
+							}
+						},
+					),
+				}
+			},
+			Cleanup: func(data test.Data, helpers test.Helpers) {
+				helpers.Anyhow("rm", "-f", tc.name)
+			},
+		})
+	}
+
+	testCase.Run(t)
+}
+
+func TestRunHealthcheckFromImage(t *testing.T) {
+	nerdtest.Setup()
+
+	dockerfile := fmt.Sprintf(`FROM %s
+HEALTHCHECK --interval=30s --timeout=10s CMD wget -q --spider http://localhost:8080 || exit 1
+	`, testutil.CommonImage)
+
+	testCase := &test.Case{
+		Require: nerdtest.Build,
+		Setup: func(data test.Data, helpers test.Helpers) {
+			data.Temp().Save(dockerfile, "Dockerfile")
+			data.Labels().Set("image", data.Identifier())
+			helpers.Ensure("build", "-t", data.Labels().Get("image"), data.Temp().Path())
+		},
+		SubTests: []*test.Case{
+			{
+				Description: "merge_with_image",
+				Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+					return helpers.Command("run", "-d", "--name", data.Identifier(),
+						"--health-retries=5",
+						"--health-interval=45s",
+						data.Labels().Get("image"))
+				},
+				Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+					return &test.Expected{
+						ExitCode: expect.ExitCodeSuccess,
+						Output: expect.All(func(stdout, _ string, t *testing.T) {
+							inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+							hc := inspect.Config.Healthcheck
+							assert.Assert(t, hc != nil, "expected healthcheck config to be present")
+							assert.DeepEqual(t, hc.Test, []string{"CMD-SHELL", "wget -q --spider http://localhost:8080 || exit 1"})
+							assert.Equal(t, 5, hc.Retries)               // From CLI flags
+							assert.Equal(t, 45*time.Second, hc.Interval) // From CLI flags
+							assert.Equal(t, 10*time.Second, hc.Timeout)  // From Dockerfile
+						}),
+					}
+				},
+				Cleanup: func(data test.Data, helpers test.Helpers) {
+					helpers.Anyhow("rm", "-f", data.Identifier())
+				},
+			},
+			{
+				Description: "Disable image health checks via runtime flag",
+				Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+					return helpers.Command(
+						"run", "-d", "--name", data.Identifier(),
+						"--no-healthcheck",
+						data.Labels().Get("image"),
+					)
+				},
+				Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+					return &test.Expected{
+						ExitCode: expect.ExitCodeSuccess,
+						Output: expect.All(func(stdout, _ string, t *testing.T) {
+							inspect := nerdtest.InspectContainer(helpers, data.Identifier())
+							hc := inspect.Config.Healthcheck
+							assert.Assert(t, hc != nil, "expected healthcheck config to be present")
+							assert.DeepEqual(t, hc.Test, []string{"NONE"})
+						}),
+					}
+				},
+				Cleanup: func(data test.Data, helpers test.Helpers) {
+					helpers.Anyhow("rm", "-f", data.Identifier())
+				},
+			},
+		},
+	}
+
+	testCase.Run(t)
+}
diff --git a/cmd/nerdctl/network/network_list_linux_test.go b/cmd/nerdctl/network/network_list_linux_test.go
index 3bf6f9e9..cb658313 100644
--- a/cmd/nerdctl/network/network_list_linux_test.go
+++ b/cmd/nerdctl/network/network_list_linux_test.go
@@ -81,6 +81,28 @@ func TestNetworkLsFilter(t *testing.T) {
 							data.Labels().Get("netID2")[:12]: {},
 						}
 
+						for _, name := range lines {
+							_, ok := netNames[name]
+							assert.Assert(t, ok, info)
+						}
+					},
+				}
+			},
+		},
+		{
+			Description: "filter name regexp",
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("network", "ls", "--quiet", "--filter", "name=.*"+data.Labels().Get("net2")+".*")
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					Output: func(stdout string, info string, t *testing.T) {
+						var lines = strings.Split(strings.TrimSpace(stdout), "\n")
+						assert.Assert(t, len(lines) >= 1, info)
+						netNames := map[string]struct{}{
+							data.Labels().Get("netID2")[:12]: {},
+						}
+
 						for _, name := range lines {
 							_, ok := netNames[name]
 							assert.Assert(t, ok, info)
diff --git a/cmd/nerdctl/volume/volume_list_test.go b/cmd/nerdctl/volume/volume_list_test.go
index 8dca19fa..8535f55b 100644
--- a/cmd/nerdctl/volume/volume_list_test.go
+++ b/cmd/nerdctl/volume/volume_list_test.go
@@ -280,6 +280,27 @@ func TestVolumeLsFilter(t *testing.T) {
 				}
 			},
 		},
+		{
+			Description: "Retrieving name=.*volume1.*",
+			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
+				return helpers.Command("volume", "ls", "--quiet", "--filter", "name=.*"+data.Labels().Get("vol1")+".*")
+			},
+			Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
+				return &test.Expected{
+					Output: func(stdout string, info string, t *testing.T) {
+						var lines = strings.Split(strings.TrimSpace(stdout), "\n")
+						assert.Assert(t, len(lines) >= 1, "expected at least 1 line"+info)
+						volNames := map[string]struct{}{
+							data.Labels().Get("vol1"): {},
+						}
+						for _, name := range lines {
+							_, ok := volNames[name]
+							assert.Assert(t, ok, fmt.Sprintf("unexpected volume %s found", name)+info)
+						}
+					},
+				}
+			},
+		},
 		{
 			Description: "Retrieving name=volume1 and name=volume2",
 			Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
diff --git a/pkg/inspecttypes/dockercompat/dockercompat_test.go b/pkg/inspecttypes/dockercompat/dockercompat_test.go
index e2712176..3e7602d8 100644
--- a/pkg/inspecttypes/dockercompat/dockercompat_test.go
+++ b/pkg/inspecttypes/dockercompat/dockercompat_test.go
@@ -22,16 +22,22 @@ import (
 	"path/filepath"
 	"runtime"
 	"testing"
+	"time"
 
 	"github.com/docker/go-connections/nat"
+	"github.com/opencontainers/go-digest"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/opencontainers/runtime-spec/specs-go"
 	"gotest.tools/v3/assert"
 
 	containerd "github.com/containerd/containerd/v2/client"
 	"github.com/containerd/containerd/v2/core/containers"
+	"github.com/containerd/containerd/v2/core/images"
 
+	"github.com/containerd/nerdctl/v2/pkg/healthcheck"
 	"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native"
 	"github.com/containerd/nerdctl/v2/pkg/internal/filesystem"
+	"github.com/containerd/nerdctl/v2/pkg/labels"
 )
 
 func TestContainerFromNative(t *testing.T) {
@@ -42,6 +48,16 @@ func TestContainerFromNative(t *testing.T) {
 	filesystem.WriteFile(filepath.Join(tempStateDir, "resolv.conf"), []byte(""), 0644)
 	defer os.RemoveAll(tempStateDir)
 
+	hc := &healthcheck.Healthcheck{
+		Test:        []string{"CMD-SHELL", "curl -f http://localhost || exit 1"},
+		Interval:    time.Second * 30,
+		Timeout:     time.Second * 5,
+		Retries:     3,
+		StartPeriod: time.Second * 10,
+	}
+	hcJSON, err := hc.ToJSONString()
+	assert.NilError(t, err)
+
 	testcase := []struct {
 		name     string
 		n        *native.Container
@@ -299,6 +315,51 @@ func TestContainerFromNative(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "container with healthcheck label",
+			n: &native.Container{
+				Container: containers.Container{
+					Labels: map[string]string{
+						labels.HealthCheck: hcJSON,
+					},
+				},
+				Spec: &specs.Spec{},
+				Process: &native.Process{
+					Status: containerd.Status{
+						Status: "running",
+					},
+				},
+			},
+			expected: &Container{
+				Created:  "0001-01-01T00:00:00Z",
+				Platform: runtime.GOOS,
+				Mounts:   []MountPoint{},
+				State: &ContainerState{
+					Status:     "running",
+					Running:    true,
+					Pid:        0,
+					FinishedAt: "",
+				},
+				HostConfig: &HostConfig{
+					LogConfig:          loggerLogConfig{Driver: "json-file", Opts: map[string]string{}},
+					PortBindings:       nat.PortMap{},
+					GroupAdd:           []string{},
+					Tmpfs:              map[string]string{},
+					UTSMode:            "host",
+					LinuxBlkioSettings: getDefaultLinuxBlkioSettings(),
+				},
+				NetworkSettings: &NetworkSettings{
+					Ports:    &nat.PortMap{},
+					Networks: map[string]*NetworkEndpointSettings{},
+				},
+				Config: &Config{
+					Labels: map[string]string{
+						labels.HealthCheck: hcJSON,
+					},
+					Healthcheck: hc,
+				},
+			},
+		},
 	}
 
 	for _, tc := range testcase {
@@ -512,3 +573,95 @@ func TestCpuSettingsFromNative(t *testing.T) {
 		})
 	}
 }
+
+func TestImageFromNative(t *testing.T) {
+	t.Run("parses RepoTags/Digests and RootFS Layers", func(t *testing.T) {
+		createdTime := time.Now().UTC()
+
+		img := native.Image{
+			Image: images.Image{
+				Name: "myrepo/myimage:custom",
+				Target: ocispec.Descriptor{
+					Digest: digest.Digest("sha256:targetdigest"),
+				},
+			},
+			ImageConfigDesc: ocispec.Descriptor{
+				Digest: digest.Digest("sha256:configdigest"),
+			},
+			ImageConfig: ocispec.Image{
+				RootFS: ocispec.RootFS{
+					Type:    "layers",
+					DiffIDs: []digest.Digest{"sha256:layer1", "sha256:layer2"},
+				},
+				History: []ocispec.History{
+					{
+						Created: &createdTime,
+						Author:  "test-author",
+						Comment: "test-comment",
+					},
+				},
+			},
+		}
+
+		out, err := ImageFromNative(&img)
+		assert.NilError(t, err)
+
+		// ID, tags, digests
+		assert.Equal(t, out.ID, "sha256:configdigest")
+		assert.Equal(t, out.RepoTags[0], "myrepo/myimage:custom")
+		assert.Equal(t, out.RepoDigests[0], "myrepo/myimage@sha256:targetdigest")
+
+		// RootFS
+		assert.DeepEqual(t, out.RootFS.Layers, []string{"sha256:layer1", "sha256:layer2"})
+
+		// History
+		assert.Equal(t, out.Author, "test-author")
+		assert.Equal(t, out.Comment, "test-comment")
+		assert.Equal(t, out.Created, createdTime.Format(time.RFC3339Nano))
+	})
+
+	t.Run("parses Healthcheck label", func(t *testing.T) {
+		testcases := []struct {
+			name     string
+			labels   map[string]string
+			expected *healthcheck.Healthcheck
+		}{
+			{
+				name: "Valid Healthcheck Label",
+				labels: map[string]string{
+					labels.HealthCheck: `{
+						"test": ["CMD-SHELL", "curl -f http://localhost/ || exit 1"],
+						"interval": 30000000000,
+						"timeout": 5000000000
+					}`,
+				},
+				expected: &healthcheck.Healthcheck{
+					Test:     []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"},
+					Interval: time.Second * 30,
+					Timeout:  time.Second * 5,
+				},
+			},
+			{
+				name:     "No Healthcheck Label",
+				labels:   map[string]string{},
+				expected: nil,
+			},
+		}
+
+		for _, tc := range testcases {
+			t.Run(tc.name, func(t *testing.T) {
+				img := native.Image{
+					ImageConfig: ocispec.Image{
+						Config: ocispec.ImageConfig{
+							Labels: tc.labels,
+						},
+					},
+				}
+
+				out, err := ImageFromNative(&img)
+				assert.NilError(t, err)
+				assert.DeepEqual(t, out.Config.Healthcheck, tc.expected)
+			})
+		}
+	})
+}