Repository: containerd/nerdctl

Issue:
nerdctl compose file map multiple ports
### What is the problem you're trying to solve

when compose.yml file include follows code will get error:
```
version: "3"
services:
  flit-accessor:
    image: flit-accessor:${FlitAccessorVersion}
    container_name: flit-accessor
    ports:
      - "22200-22299:22200-22299"

....
```

![image](https://user-images.githubusercontent.com/20454889/174519248-22b148a0-fdd7-4f8d-9dab-b14b71b7331f.png)

and this operation will cause ```~/.local/share/nerdctl/1935db59/names/default/flit-accessor ``` file ,which will cause the container to fail to be created later
![image](https://user-images.githubusercontent.com/20454889/174519635-328bb6e3-48bb-4ace-87d4-724c06fe017c.png)
![image](https://user-images.githubusercontent.com/20454889/174519688-39696705-c780-4576-9ee6-29fba663e085.png)



### Describe the solution you'd like

nerdctl compose file can map multiple ports and when compose failed can still create service

### Additional context

_No response_

Discussion:
@fahedouch: to unblock the situation you can remove `rm  /var/lib/nerdctl/1935db59/names/default/flit-accessor`@fahedouch: Fixed by https://github.com/containerd/nerdctl/pull/1151@chawson: ### `compose.yaml`: service part
```yaml
mozi-zlm:
    image: nginx:1.27.4
    ports:
      - '32000-32200:32000-32200'
      - '32000-32200:32000-32200/udp'
```

### `nerdctl compose up -d` received

![Image](https://github.com/user-attachments/assets/269963c7-6f3f-4cad-846b-1a15fbb1b1a0)

![Image](https://github.com/user-attachments/assets/d4987dfe-3881-4552-b5a0-0c330dd5f546)

### `nerdctl version`
Client:
 Version:	v2.0.2
 OS/Arch:	linux/amd64
 Git commit:	1220ce7ec2701d485a9b1beeea63dae3da134fb5
 buildctl:
  Version:	v0.18.1
  GitCommit:	eb68885955169461d72dc2b7e6d084100fcaba86

### `nerdctl info`
Client:
 Namespace:	default
 Debug Mode:	false

Server:
 Server Version: v2.0.1
 Storage Driver: overlayfs
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Log:     fluentd journald json-file none syslog
  Storage: native overlayfs
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version:   6.14.0-061400rc5-generic
 Operating System: Linux Mint 22.1
 OSType:           linux
 Architecture:     x86_64
 CPUs:             6
 Total Memory:     15.54GiB


Solution patch:
diff --git a/Dockerfile b/Dockerfile
index 6990d2ba..fc10cb3e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -235,7 +235,7 @@ RUN apt-get update && \
   apparmor \
   ca-certificates curl \
   iproute2 iptables \
-  dbus systemd systemd-sysv \
+  dbus dbus-user-session systemd systemd-sysv \
   fuse3
 ARG CONTAINERIZED_SYSTEMD_VERSION
 RUN curl -L -o /docker-entrypoint.sh https://raw.githubusercontent.com/AkihiroSuda/containerized-systemd/${CONTAINERIZED_SYSTEMD_VERSION}/docker-entrypoint.sh && \
@@ -290,7 +290,6 @@ FROM test-integration AS test-integration-rootless
 RUN apt-get update && \
   apt-get install -qq -y \
   uidmap \
-  dbus-user-session \
   openssh-server openssh-client
 # TODO: update containerized-systemd to enable sshd by default, or allow `systemctl wants <TARGET> sshd` here
 RUN ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N '' && \
diff --git a/README.md b/README.md
index 2b9480d2..d6769aa0 100644
--- a/README.md
+++ b/README.md
@@ -282,6 +282,7 @@ It does not necessarily mean that the corresponding features are missing in cont
     - [:whale: nerdctl rmi](#whale-nerdctl-rmi)
     - [:whale: nerdctl image inspect](#whale-nerdctl-image-inspect)
     - [:whale: nerdctl image history](#whale-nerdctl-image-history)
+    - [:whale: nerdctl image prune](#whale-nerdctl-image-prune)
     - [:nerd_face: nerdctl image convert](#nerd_face-nerdctl-image-convert)
     - [:nerd_face: nerdctl image encrypt](#nerd_face-nerdctl-image-encrypt)
     - [:nerd_face: nerdctl image decrypt](#nerd_face-nerdctl-image-decrypt)
@@ -988,6 +989,19 @@ Flags:
 - :whale: `-q, --quiet`: Only display snapshots IDs
 - :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`
 
+### :whale: nerdctl image prune
+Remove unused images.
+
+:warning: Currently, `nerdctl image prune` requires --all to be specified.
+
+Usage: `nerdctl image prune [OPTIONS]`
+
+Flags:
+- :whale: `-a, --all`: Remove all unused images, not just dangling ones
+- :whale: `-f, --force`: Do not prompt for confirmation
+
+Unimplemented `docker image prune` flags: `--filter`
+
 ### :nerd_face: nerdctl image convert
 Convert an image format.
 
@@ -1514,9 +1528,6 @@ Container management:
 
 Image:
 - `docker export` and `docker import`
-
-- `docker image prune`
-
 - `docker trust *` (Instead, nerdctl supports `nerdctl pull --verify=cosign` and `nerdctl push --sign=cosign`. See [`./docs/cosign.md`](docs/cosign.md).)
 - `docker manifest *`
 
diff --git a/cmd/nerdctl/build.go b/cmd/nerdctl/build.go
index 2a6575b4..b7514eab 100644
--- a/cmd/nerdctl/build.go
+++ b/cmd/nerdctl/build.go
@@ -327,7 +327,6 @@ func generateBuildctlArgs(cmd *cobra.Command, buildkitHost string, platform, arg
 		"--progress=" + progressValue,
 		"--frontend=dockerfile.v0",
 		"--local=context=" + buildContext,
-		"--local=dockerfile=" + buildContext,
 		"--output=" + output,
 	}...)
 
@@ -336,8 +335,8 @@ func generateBuildctlArgs(cmd *cobra.Command, buildkitHost string, platform, arg
 		return "", nil, false, "", nil, nil, err
 	}
 
-	var dir, file string
-
+	dir := buildContext
+	file := buildkitutil.DefaultDockerfileName
 	if filename != "" {
 		if filename == "-" {
 			var err error
@@ -345,7 +344,6 @@ func generateBuildctlArgs(cmd *cobra.Command, buildkitHost string, platform, arg
 			if err != nil {
 				return "", nil, false, "", nil, nil, err
 			}
-			file = buildkitutil.DefaultDockerfileName
 			cleanup = func() {
 				os.RemoveAll(dir)
 			}
@@ -353,11 +351,19 @@ func generateBuildctlArgs(cmd *cobra.Command, buildkitHost string, platform, arg
 			dir, file = filepath.Split(filename)
 		}
 
-		if dir != "" {
-			buildctlArgs = append(buildctlArgs, "--local=dockerfile="+dir)
+		if dir == "" {
+			dir = "."
 		}
-		buildctlArgs = append(buildctlArgs, "--opt=filename="+file)
 	}
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		return "", nil, false, "", nil, nil, err
+	}
+	if _, err := os.Lstat(filepath.Join(absDir, file)); err != nil {
+		return "", nil, false, "", nil, nil, err
+	}
+	buildctlArgs = append(buildctlArgs, "--local=dockerfile="+dir)
+	buildctlArgs = append(buildctlArgs, "--opt=filename="+file)
 
 	target, err := cmd.Flags().GetString("target")
 	if err != nil {
diff --git a/cmd/nerdctl/create.go b/cmd/nerdctl/create.go
index ea39f4ac..28416aaf 100644
--- a/cmd/nerdctl/create.go
+++ b/cmd/nerdctl/create.go
@@ -60,8 +60,11 @@ func createAction(cmd *cobra.Command, args []string) error {
 	}
 	defer cancel()
 
-	container, err := createContainer(cmd, ctx, client, args, platform, false, false, true)
+	container, gc, err := createContainer(cmd, ctx, client, args, platform, false, false, true)
 	if err != nil {
+		if gc != nil {
+			gc()
+		}
 		return err
 	}
 	fmt.Fprintln(cmd.OutOrStdout(), container.ID())
diff --git a/cmd/nerdctl/image.go b/cmd/nerdctl/image.go
index 0c7a2fa2..afae1936 100644
--- a/cmd/nerdctl/image.go
+++ b/cmd/nerdctl/image.go
@@ -44,6 +44,7 @@ func newImageCommand() *cobra.Command {
 		newImageInspectCommand(),
 		newImageEncryptCommand(),
 		newImageDecryptCommand(),
+		newImagePruneCommand(),
 	)
 	return cmd
 }
diff --git a/cmd/nerdctl/image_prune.go b/cmd/nerdctl/image_prune.go
new file mode 100644
index 00000000..a863873f
--- /dev/null
+++ b/cmd/nerdctl/image_prune.go
@@ -0,0 +1,116 @@
+/*
+   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 main
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/containerd/containerd/images"
+	"github.com/containerd/containerd/platforms"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+)
+
+func newImagePruneCommand() *cobra.Command {
+	imagePruneCommand := &cobra.Command{
+		Use:           "prune [flags]",
+		Short:         "Remove unused images",
+		Args:          cobra.NoArgs,
+		RunE:          imagePruneAction,
+		SilenceUsage:  true,
+		SilenceErrors: true,
+	}
+
+	imagePruneCommand.Flags().BoolP("all", "a", false, "Remove all unused images, not just dangling ones")
+	imagePruneCommand.Flags().BoolP("force", "f", false, "Do not prompt for confirmation")
+	return imagePruneCommand
+}
+
+func imagePruneAction(cmd *cobra.Command, _ []string) error {
+	client, ctx, cancel, err := newClient(cmd)
+	if err != nil {
+		return err
+	}
+	defer cancel()
+
+	all, err := cmd.Flags().GetBool("all")
+	if err != nil {
+		return err
+	}
+
+	if !all {
+		logrus.Warn("Currently, `nerdctl image prune` requires --all to be specified. Skip pruning.")
+		// NOP
+		return nil
+	}
+
+	force, err := cmd.Flags().GetBool("force")
+	if err != nil {
+		return err
+	}
+
+	if !force {
+		var confirm string
+		msg := "This will remove all images without at least one container associated to them."
+		msg += "\nAre you sure you want to continue? [y/N] "
+
+		fmt.Fprintf(cmd.OutOrStdout(), "WARNING! %s", msg)
+		fmt.Fscanf(cmd.InOrStdin(), "%s", &confirm)
+
+		if strings.ToLower(confirm) != "y" {
+			return nil
+		}
+	}
+	var (
+		imageStore     = client.ImageService()
+		contentStore   = client.ContentStore()
+		containerStore = client.ContainerService()
+	)
+	imageList, err := imageStore.List(ctx)
+	if err != nil {
+		return err
+	}
+	containerList, err := containerStore.List(ctx)
+	if err != nil {
+		return err
+	}
+	usedImages := make(map[string]struct{})
+	for _, container := range containerList {
+		usedImages[container.Image] = struct{}{}
+	}
+
+	delOpts := []images.DeleteOpt{images.SynchronousDelete()}
+	for _, image := range imageList {
+		if _, ok := usedImages[image.Name]; ok {
+			continue
+		}
+
+		digests, err := image.RootFS(ctx, contentStore, platforms.DefaultStrict())
+		if err != nil {
+			logrus.WithError(err).Warnf("failed to enumerate rootfs")
+		}
+		if err := imageStore.Delete(ctx, image.Name, delOpts...); err != nil {
+			logrus.WithError(err).Warnf("failed to delete image %s", image.Name)
+		}
+		fmt.Fprintf(cmd.OutOrStdout(), "Untagged: %s\n", image.Name)
+		for _, digest := range digests {
+			fmt.Fprintf(cmd.OutOrStdout(), "Deleted: %s\n", digest)
+		}
+	}
+	return nil
+}
diff --git a/cmd/nerdctl/images.go b/cmd/nerdctl/images.go
index 45982a1b..07f49a98 100644
--- a/cmd/nerdctl/images.go
+++ b/cmd/nerdctl/images.go
@@ -102,7 +102,6 @@ func imagesAction(cmd *cobra.Command, args []string) error {
 
 	var (
 		imageStore = client.ImageService()
-		cs         = client.ContentStore()
 	)
 
 	// To-do: Add support for --filter.
@@ -111,7 +110,7 @@ func imagesAction(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	return printImages(ctx, cmd, client, imageList, cs)
+	return printImages(ctx, cmd, client, imageList)
 }
 
 type imagePrintable struct {
@@ -129,7 +128,7 @@ type imagePrintable struct {
 	Platform string // nerdctl extension
 }
 
-func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Client, imageList []images.Image, cs content.Store) error {
+func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Client, imageList []images.Image) error {
 	quiet, err := cmd.Flags().GetBool("quiet")
 	if err != nil {
 		return err
diff --git a/cmd/nerdctl/ps.go b/cmd/nerdctl/ps.go
index fbea4c99..a184a45b 100644
--- a/cmd/nerdctl/ps.go
+++ b/cmd/nerdctl/ps.go
@@ -162,7 +162,7 @@ func printContainers(ctx context.Context, client *containerd.Client, cmd *cobra.
 	case "wide":
 		w = tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
 		if !quiet {
-			fmt.Fprintln(w, "CONTAINER ID\tIMAGE\tPLATFORM\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tSIZE")
+			fmt.Fprintln(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tPLATFORM\tSIZE")
 			wide = true
 		}
 	default:
@@ -239,48 +239,27 @@ func printContainers(ctx context.Context, client *containerd.Client, cmd *cobra.
 				return err
 			}
 		} else {
+			format := "%s\t%s\t%s\t%s\t%s\t%s\t%s"
+			args := []interface{}{
+				p.ID,
+				p.Image,
+				p.Command,
+				formatter.TimeSinceInHuman(info.CreatedAt),
+				p.Status,
+				p.Ports,
+				p.Names,
+			}
 			if wide {
-				if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
-					p.ID,
-					p.Image,
-					p.Platform,
-					p.Command,
-					formatter.TimeSinceInHuman(info.CreatedAt),
-					p.Status,
-					p.Ports,
-					p.Names,
-					p.Runtime,
-					p.Size,
-				); err != nil {
-					return err
-				}
+				format += "\t%s\t%s\t%s\n"
+				args = append(args, p.Runtime, p.Platform, p.Size)
+			} else if size {
+				format += "\t%s\n"
+				args = append(args, p.Size)
 			} else {
-				if size {
-					_, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
-						p.ID,
-						p.Image,
-						p.Command,
-						formatter.TimeSinceInHuman(info.CreatedAt),
-						p.Status,
-						p.Ports,
-						p.Names,
-						p.Size,
-					)
-				} else {
-					_, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
-						p.ID,
-						p.Image,
-						p.Command,
-						formatter.TimeSinceInHuman(info.CreatedAt),
-						p.Status,
-						p.Ports,
-						p.Names,
-					)
-				}
-
-				if err != nil {
-					return err
-				}
+				format += "\n"
+			}
+			if _, err := fmt.Fprintf(w, format, args...); err != nil {
+				return err
 			}
 		}
 
diff --git a/cmd/nerdctl/run.go b/cmd/nerdctl/run.go
index 41f256f7..77324c2d 100644
--- a/cmd/nerdctl/run.go
+++ b/cmd/nerdctl/run.go
@@ -295,8 +295,11 @@ func runAction(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
-	container, err := createContainer(cmd, ctx, client, args, platform, flagI, flagT, flagD)
+	container, gc, err := createContainer(cmd, ctx, client, args, platform, flagI, flagT, flagD)
 	if err != nil {
+		if gc != nil {
+			defer gc()
+		}
 		return err
 	}
 
@@ -380,7 +383,7 @@ func runAction(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd.Client, args []string, platform string, flagI, flagT, flagD bool) (containerd.Container, error) {
+func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd.Client, args []string, platform string, flagI, flagT, flagD bool) (containerd.Container, func(), error) {
 	// simulate the behavior of double dash
 	newArg := []string{}
 	if len(args) >= 2 && args[1] == "--" {
@@ -391,7 +394,7 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	ns, err := cmd.Flags().GetString("namespace")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	var (
@@ -402,25 +405,25 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	cidfile, err := cmd.Flags().GetString("cidfile")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if cidfile != "" {
 		if err := writeCIDFile(cidfile, id); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 	}
 
 	dataStore, err := getDataStore(cmd)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	stateDir, err := getContainerStateDirPath(cmd, dataStore, id)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if err := os.MkdirAll(stateDir, 0700); err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	opts = append(opts,
@@ -429,19 +432,19 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	opts, err = setPlatformOptions(opts, cmd, id)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	rootfsOpts, rootfsCOpts, ensuredImage, err := generateRootfsOpts(ctx, client, platform, cmd, args, id)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	opts = append(opts, rootfsOpts...)
 	cOpts = append(cOpts, rootfsCOpts...)
 
 	wd, err := cmd.Flags().GetString("workdir")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if wd != "" {
 		opts = append(opts, oci.WithProcessCwd(wd))
@@ -449,19 +452,19 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	envFile, err := cmd.Flags().GetStringSlice("env-file")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if envFiles := strutil.DedupeStrSlice(envFile); len(envFiles) > 0 {
 		env, err := parseEnvVars(envFiles)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		opts = append(opts, oci.WithEnv(env))
 	}
 
 	env, err := cmd.Flags().GetStringArray("env")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if env := strutil.DedupeStrSlice(env); len(env) > 0 {
 		opts = append(opts, oci.WithEnv(env))
@@ -469,20 +472,20 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	if flagI {
 		if flagD {
-			return nil, errors.New("currently flag -i and -d cannot be specified together (FIXME)")
+			return nil, nil, errors.New("currently flag -i and -d cannot be specified together (FIXME)")
 		}
 	}
 
 	if flagT {
 		if flagD {
-			return nil, errors.New("currently flag -t and -d cannot be specified together (FIXME)")
+			return nil, nil, errors.New("currently flag -t and -d cannot be specified together (FIXME)")
 		}
 		opts = append(opts, oci.WithTTY)
 	}
 
 	mountOpts, anonVolumes, mountPoints, err := generateMountOpts(cmd, ctx, client, ensuredImage)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	} else {
 		opts = append(opts, mountOpts...)
 	}
@@ -492,18 +495,18 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 		// json-file is the built-in and default log driver for nerdctl
 		logDriver, err := cmd.Flags().GetString("log-driver")
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		logOptMap, err := parseKVStringsMapFromLogOpt(cmd, logDriver)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		logDriverInst, err := logging.GetDriver(logDriver, logOptMap)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		if err := logDriverInst.Init(dataStore, ns, id); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		logConfig := &logging.LogConfig{
 			Driver: logDriver,
@@ -511,14 +514,14 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 		}
 		logConfigB, err := json.Marshal(logConfig)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		logConfigFilePath := logging.LogConfigFilePath(dataStore, ns, id)
 		if err = os.WriteFile(logConfigFilePath, logConfigB, 0600); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		if lu, err := generateLogURI(dataStore); err != nil {
-			return nil, err
+			return nil, nil, err
 		} else if lu != nil {
 			logURI = lu.String()
 		}
@@ -526,34 +529,34 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	restartValue, err := cmd.Flags().GetString("restart")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	restartOpts, err := generateRestartOpts(ctx, client, restartValue, logURI)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	cOpts = append(cOpts, restartOpts...)
 
 	stopSignal, err := cmd.Flags().GetString("stop-signal")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	stopTimeout, err := cmd.Flags().GetInt("stop-timeout")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	cOpts = append(cOpts, withStop(stopSignal, stopTimeout, ensuredImage))
 
 	netOpts, netSlice, ipAddress, ports, err := generateNetOpts(cmd, dataStore, stateDir, ns, id)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	opts = append(opts, netOpts...)
 
 	hostname := id[0:12]
 	customHostname, err := cmd.Flags().GetString("hostname")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if customHostname != "" {
 		hostname = customHostname
@@ -563,51 +566,51 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 	if runtime.GOOS == "linux" {
 		hostnamePath := filepath.Join(stateDir, "hostname")
 		if err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0644); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		opts = append(opts, withCustomEtcHostname(hostnamePath))
 	}
 
 	hookOpt, err := withNerdctlOCIHook(cmd, id, stateDir)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	opts = append(opts, hookOpt)
 
 	if uOpts, err := generateUserOpts(cmd); err != nil {
-		return nil, err
+		return nil, nil, err
 	} else {
 		opts = append(opts, uOpts...)
 	}
 
 	if gOpts, err := generateGroupsOpts(cmd); err != nil {
-		return nil, err
+		return nil, nil, err
 	} else {
 		opts = append(opts, gOpts...)
 	}
 
 	if umaskOpts, err := generateUmaskOpts(cmd); err != nil {
-		return nil, err
+		return nil, nil, err
 	} else {
 		opts = append(opts, umaskOpts...)
 	}
 
 	rtCOpts, err := generateRuntimeCOpts(cmd)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	cOpts = append(cOpts, rtCOpts...)
 
 	lCOpts, err := withContainerLabels(cmd)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	cOpts = append(cOpts, lCOpts...)
 
 	var containerNameStore namestore.NameStore
 	name, err := cmd.Flags().GetString("name")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if name == "" && !cmd.Flags().Changed("name") {
 		// Automatically set the container name, unless `--name=""` was explicitly specified.
@@ -620,10 +623,10 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 	if name != "" {
 		containerNameStore, err = namestore.New(dataStore, ns)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		if err := containerNameStore.Acquire(name, id); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 	}
 
@@ -631,23 +634,23 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 	if cmd.Flags().Lookup("pidfile").Changed {
 		pidFile, err = cmd.Flags().GetString("pidfile")
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 	}
 
 	extraHosts, err := cmd.Flags().GetStringSlice("add-host")
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	extraHosts = strutil.DedupeStrSlice(extraHosts)
 	for _, host := range extraHosts {
 		if _, err := dopts.ValidateExtraHost(host); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 	}
 	ilOpt, err := withInternalLabels(ns, name, hostname, stateDir, extraHosts, netSlice, ipAddress, ports, logURI, anonVolumes, pidFile, platform, mountPoints)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	cOpts = append(cOpts, ilOpt)
 
@@ -659,9 +662,28 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
 
 	container, err := client.NewContainer(ctx, id, cOpts...)
 	if err != nil {
-		return nil, err
+		gcContainer := func() {
+			var isErr bool
+			if errE := os.RemoveAll(stateDir); errE != nil {
+				isErr = true
+			}
+			if name != "" {
+				var errE error
+				if containerNameStore, errE = namestore.New(dataStore, ns); errE != nil {
+					isErr = true
+				}
+				if errE = containerNameStore.Release(name, id); errE != nil {
+					isErr = true
+				}
+
+			}
+			if isErr {
+				logrus.Warnf("failed to remove container %q", id)
+			}
+		}
+		return nil, gcContainer, err
 	}
-	return container, nil
+	return container, nil, nil
 }
 
 func generateRootfsOpts(ctx context.Context, client *containerd.Client, platform string, cmd *cobra.Command, args []string, id string) ([]oci.SpecOpts, []containerd.NewContainerOpts, *imgutil.EnsuredImage, error) {
diff --git a/cmd/nerdctl/stats.go b/cmd/nerdctl/stats.go
index 1871507e..9dba06b6 100644
--- a/cmd/nerdctl/stats.go
+++ b/cmd/nerdctl/stats.go
@@ -134,6 +134,9 @@ func statsAction(cmd *cobra.Command, args []string) error {
 		return errors.New("unsupported format: \"raw\"")
 	default:
 		tmpl, err = parseTemplate(format)
+		if err != nil {
+			return err
+		}
 	}
 
 	noTrunc, err := cmd.Flags().GetBool("no-trunc")
@@ -312,7 +315,7 @@ func statsAction(cmd *cobra.Command, args []string) error {
 		}
 		cStats.mu.Unlock()
 
-		// print header for every tab
+		// print header for every tick
 		if format == "" || format == "table" {
 			fmt.Fprintln(w, "CONTAINER ID\tNAME\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS")
 		}



Test patch:
diff --git a/cmd/nerdctl/build_test.go b/cmd/nerdctl/build_test.go
index e0d21688..7ee0ab32 100644
--- a/cmd/nerdctl/build_test.go
+++ b/cmd/nerdctl/build_test.go
@@ -140,6 +140,38 @@ CMD ["echo", "nerdctl-build-test-stdin"]
 	base.Cmd("build", "-t", imageName, "-f", "-", ".").CmdOption(testutil.WithStdin(strings.NewReader(dockerfile))).AssertCombinedOutContains(imageName)
 }
 
+func TestBuildWithDockerfile(t *testing.T) {
+	testutil.RequiresBuild(t)
+	base := testutil.NewBase(t)
+	defer base.Cmd("builder", "prune").Run()
+	imageName := testutil.Identifier(t)
+	defer base.Cmd("rmi", imageName).Run()
+
+	dockerfile := fmt.Sprintf(`FROM %s
+CMD ["echo", "nerdctl-build-test-dockerfile"]
+	`, testutil.CommonImage)
+
+	buildCtx := filepath.Join(t.TempDir(), "test")
+	err := os.MkdirAll(buildCtx, 0755)
+	assert.NilError(t, err)
+	err = os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0644)
+	assert.NilError(t, err)
+
+	pwd, err := os.Getwd()
+	assert.NilError(t, err)
+	err = os.Chdir(buildCtx)
+	assert.NilError(t, err)
+	defer os.Chdir(pwd)
+
+	// hack os.Getwd return "(unreachable)" on rootless
+	t.Setenv("PWD", buildCtx)
+
+	base.Cmd("build", "-t", imageName, "-f", "Dockerfile", "..").AssertOK()
+	base.Cmd("build", "-t", imageName, "-f", "Dockerfile", ".").AssertOK()
+	// fail err: no such file or directory
+	base.Cmd("build", "-t", imageName, "-f", "../Dockerfile", ".").AssertFail()
+}
+
 func TestBuildLocal(t *testing.T) {
 	t.Parallel()
 	testutil.DockerIncompatible(t)
diff --git a/cmd/nerdctl/image_prune_test.go b/cmd/nerdctl/image_prune_test.go
new file mode 100644
index 00000000..1683da76
--- /dev/null
+++ b/cmd/nerdctl/image_prune_test.go
@@ -0,0 +1,55 @@
+/*
+   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 main
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/containerd/nerdctl/pkg/testutil"
+	"gotest.tools/v3/assert"
+)
+
+func TestImagePrune(t *testing.T) {
+	testutil.RequiresBuild(t)
+
+	base := testutil.NewBase(t)
+	defer base.Cmd("builder", "prune").Run()
+	imageName := testutil.Identifier(t)
+	defer base.Cmd("rmi", imageName).Run()
+
+	dockerfile := fmt.Sprintf(`FROM %s
+	CMD ["echo", "nerdctl-test-image-prune"]`, testutil.CommonImage)
+
+	buildCtx, err := createBuildContext(dockerfile)
+	assert.NilError(t, err)
+	defer os.RemoveAll(buildCtx)
+
+	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
+	base.Cmd("images").AssertOutContains(imageName)
+
+	tID := testutil.Identifier(t)
+	base.Cmd("run", "--name", tID, imageName).AssertOK()
+	defer base.Cmd("rm", "-f", tID).Run()
+	base.Cmd("image", "prune", "--force", "--all").AssertNoOut(imageName)
+	base.Cmd("images").AssertOutContains(imageName)
+
+	base.Cmd("rm", "-f", tID).AssertOK()
+	base.Cmd("image", "prune", "--force", "--all").AssertOutContains(imageName)
+	base.Cmd("images").AssertNoOut(imageName)
+}
diff --git a/cmd/nerdctl/ps_linux_test.go b/cmd/nerdctl/ps_linux_test.go
index 8c4b3f74..2b1e8baf 100644
--- a/cmd/nerdctl/ps_linux_test.go
+++ b/cmd/nerdctl/ps_linux_test.go
@@ -110,7 +110,7 @@ func TestContainerListWideMode(t *testing.T) {
 			return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
 		}
 
-		tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tPLATFORM\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tSIZE")
+		tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tPLATFORM\tSIZE")
 		err := tab.ParseHeader(lines[0])
 		if err != nil {
 			return fmt.Errorf("failed to parse header: %v", err)
diff --git a/cmd/nerdctl/run_cgroup_linux_test.go b/cmd/nerdctl/run_cgroup_linux_test.go
index 121930be..7e92e2e1 100644
--- a/cmd/nerdctl/run_cgroup_linux_test.go
+++ b/cmd/nerdctl/run_cgroup_linux_test.go
@@ -124,7 +124,9 @@ func TestRunCgroupV2(t *testing.T) {
 
 func TestRunCgroupV1(t *testing.T) {
 	t.Parallel()
-	if cgroups.Mode() != cgroups.Legacy {
+	switch cgroups.Mode() {
+	case cgroups.Legacy, cgroups.Hybrid:
+	default:
 		t.Skip("test requires cgroup v1")
 	}
 	base := testutil.NewBase(t)
diff --git a/cmd/nerdctl/run_network_linux_test.go b/cmd/nerdctl/run_network_linux_test.go
index 956ffa29..9522e6de 100644
--- a/cmd/nerdctl/run_network_linux_test.go
+++ b/cmd/nerdctl/run_network_linux_test.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/containerd/containerd/errdefs"
 	"github.com/containerd/nerdctl/pkg/rootlessutil"
 	"github.com/containerd/nerdctl/pkg/testutil"
 	"github.com/containerd/nerdctl/pkg/testutil/nettestutil"
@@ -396,6 +397,17 @@ func TestRunPort(t *testing.T) {
 
 }
 
+func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
+	// docker does not set label restriction to 4096 bytes
+	testutil.DockerIncompatible(t)
+	t.Parallel()
+	base := testutil.NewBase(t)
+	containerName := testutil.Identifier(t)
+	base.Cmd("run", "--rm", "--name", containerName, "-p", "22200-22299:22200-22299", testutil.CommonImage).AssertFail()
+	base.Cmd("run", "--rm", "--name", containerName, "-p", "22200-22299:22200-22299", testutil.CommonImage).AssertCombinedOutContains(errdefs.ErrInvalidArgument.Error())
+	base.Cmd("run", "--rm", "--name", containerName, testutil.CommonImage).AssertOK()
+}
+
 func TestRunContainerWithStaticIP(t *testing.T) {
 	if rootlessutil.IsRootless() {
 		t.Skip("Static IP assignment is not supported rootless mode yet.")