Browse Source

package/podman: new package

Podman is a container manager not unlike Docker, but is daemon-less.

Similarly to docker-engine, quite a few kernel config options are
required; as they are very similar in goals and features, the options
from docker-engine have been duplicated for podman. As that was not
enough, a few additional options have been added after trial-and-error
testing (they are not explicitly listed in the documentation).

The documentation [0] states that seccomp can be disabled (i.e. not
enabled). However, without seccomp support, starting containers requires
--security-opt=seccomp=unconfined to be specified; it does not look
trivial to make that the default, though [1]. Furthermore, containers
are about security, so disabling a security measure does not sound too
good. So we make seccomp support mandatory.

Podman needs netavark as a the network backend (it missing is a hard
error at runtime). It is supposed to also require aardvark-dns, an
authoritative DNS resolver, but it missing does not look to adversely
affect networking, so it is not used (as not even packaged in Buildroot
yet).

Podman can run either as the root user, or it can run rootless, i.e. by
a non-root user, which requires a bit of setup (see below, in the
runtime test) and a few other dependencies: slirp4netns [2] (to provide
network connectivity in userland), and support for subordinate UIDs and
GIDs with the shadow library. Rootless mode is one of the main selling
point for podman, so we decided that this would not be configurable in
Buildroot.

Similar to Docker, podman can inject a minimalist init as PID1 in
containers, and like Docker, this is optional; podman however by default
uses catatonit as such an init [3]. As for Docker-engine, we offer a
choice of which init package to use to provide podman-init.

Podman requires at least three config files; they can be either per-user
or system-wide:
  - containers.conf [4]: defines various settings for the container
    runtimes;
  - policy.json [5]: defines what signature to accept to validate
    images; without one such file, podman just refuses to pull images;
  - registries.conf [6]: defines where to pull images from; without it,
    podman does not know how to pull un-qualified images (i.e. images
    where the registry is not specified in the path, and which Docker
    would fetch from the Docker Hub, e.g. "busybox:latest").

For those three files, we provide a very minimal default that (in the
same order as above):
  - uses the slirp4netns network backend for rootless operation (the
    default in podman is to use pasta [1], so we need to explicitly
    configure it to use slirp4netns);
  - allows pulling images which signature can't be verified;
  - pulls unqualified images from the Docker Hub, as is traditional.

Providing actual files is going to be use-case dependent, and interested
parties will have to provide their own config files, e.g. in a rootfs
overlay.

Finally, we add a runtime test for podman. Podman is a huge binary, and
may call other huge binaries (netavark...); this can be quite slow in
the emulated machine (even when running on a very fast host machine), so
we use a huge timeout for all commands involving podman, even those that
exit the containers, as that may need to tear down podman setup.

The default kernel used in runtime tests is missing a lot of features,
so we need to build our own; we use the same version as the bundled
kernel. We can't use cpio either, because we need a filesystem that can
be used as a lower and upper of overlayfs, which is not possible with
the filesystem the cpio is extracted into; ext2 fits the bill, so we use
that. We need a bit of space to store images and stuff, so let's be
generous and allocate 256M.

To test rootless operation, we need a non-root user that has some
special setup [7]; it is easier to run the commands from the infra
rather than carry a user-definition table and a rootfs overlay. We need
that user to have the same prompts (main and continuation) so that the
REPLWrapper still detects those; it has the unfortunate side effect that
it is not immediately obvious whether a command was run as root or not,
and one has to look back up in the run-log to see whether there was a
transition to another user earlier.

Still for rootless containers, podman/netavark expect /etc/resolv.conf
to be either a plain file, or a symlink that points either deeper in
/etc or anywhere in /run; if resolv.conf resolves to any other location,
DNS in rootless containers does not work. This is reasonable, and is
what already happens on a systemd-based system (and thus all major
distributions nowadays. However, in Buildroot, we put the actual file in
/tmp; this is historical, and dates back to the days where Buildroot did
not have a guaranteed-writable /run. So, we work around this limitation
in the test (for now).

The official busybox image on the Docker Hub supports a lot of
architectures, of which armv7 which we use for this runtime test.
Finding a small image that also supports armv7 on other registries was
a bit of a challenge; we eventually found one busybox image on quay.io,
but it is not an official busybox image; still, it fits the bill, so we
use it.

There is no runtime test with systemd, as this requires quite some
additional setup that does not look very trivial to do; when it detects
it is running under systemd in rootless mode, podman expects that a full
user session exists, or it whines about it every time it is started,
reverting to non-systemd behaviour; getting a full user session does not
look to be that trivial (PAM?), so this would not exercise the actual
integration with systemd, so the test would not be meaningful, so it is
not provided. This is left as an exercise to an interested party to
extend the tests.

PS: Hat-tip to Raphael, who provided some pointers and hints on this
change, especially for rootless mode. Thanks! 👍

[0] https://podman.io/docs/installation#get-source-code
[1] it looks like we can provide a custom seccomp profile, by specifying
seccomp_profile="PATH" in containers.conf; that would still require
seccomp support to use that file, though, so that does not change the
outcome.
[2] it is possible to use another backend, but it is not packaged in
in Buildroot yet: https://passt.top/passt/about/#pasta-pack-a-subtle-tap-abstraction
[3] podman expects a 'catatonit' helper in /usr/libexec/podman, so even
if tini would be usable instead, it would not feel right to use it to
impersonate catatonit. So let's assume that only catatonit is supported.
[4] https://github.com/containers/common/blob/main/docs/containers.conf.5.md
[5] https://github.com/containers/image/blob/main/docs/containers-policy.json.5.md
[6] https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md
[7] https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md

Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Cc: Raphael Pavlidis <raphael.pavlidis@gmail.com>
Cc: Christian Stewart <christian@aperture.us>
Cc: Julien Olivain <ju.o@free.fr>
Signed-off-by: Julien Olivain <ju.o@free.fr>
Yann E. MORIN 4 months ago
parent
commit
ebbaac08e3

+ 1 - 0
package/Config.in

@@ -2869,6 +2869,7 @@ menu "System tools"
 	source "package/openvmtools/Config.in"
 	source "package/openvmtools/Config.in"
 	source "package/pamtester/Config.in"
 	source "package/pamtester/Config.in"
 	source "package/petitboot/Config.in"
 	source "package/petitboot/Config.in"
+	source "package/podman/Config.in"
 	source "package/polkit/Config.in"
 	source "package/polkit/Config.in"
 	source "package/powerpc-utils/Config.in"
 	source "package/powerpc-utils/Config.in"
 	source "package/procps-ng/Config.in"
 	source "package/procps-ng/Config.in"

+ 80 - 0
package/podman/Config.in

@@ -0,0 +1,80 @@
+config BR2_PACKAGE_PODMAN
+	bool "podman"
+	depends on BR2_USE_MMU  # fork()
+	depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS  # host-go
+	depends on BR2_PACKAGE_HOST_RUSTC_TARGET_ARCH_SUPPORTS  # netavark
+	depends on BR2_PACKAGE_LIBGPG_ERROR_ARCH_SUPPORTS  # libgpgme
+	depends on BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS  # libseccomp, slirp4netns
+	depends on BR2_TOOLCHAIN_HEADERS_AT_LEAST_3_17  # libseccomp, slirp4netns
+	depends on BR2_TOOLCHAIN_HAS_THREADS  # conmon, slirp4netns
+	depends on BR2_USE_WCHAR  # conmon, slirp4netns
+	select BR2_PACKAGE_HOST_GO
+	select BR2_PACKAGE_CA_CERTIFICATES  # runtime
+	select BR2_PACKAGE_CGROUPFS_V2_MOUNT if !BR2_PACKAGE_CGROUPFS_MOUNT && !BR2_INIT_SYSTEMD  # runtime
+	select BR2_PACKAGE_CONMON  # runtime
+	select BR2_PACKAGE_LIBGPGME
+	select BR2_PACKAGE_LIBSECCOMP
+	select BR2_PACKAGE_NETAVARK  # runtime
+	# podman officially only supports crun, but compatible runtimes are
+	# also accepted. So we do the select the other way around, to match
+	# docker-engine's own select and thus avoid circular dependency
+	# issues.
+	select BR2_PACKAGE_RUNC if !BR2_PACKAGE_CRUN  # runtime
+	select BR2_PACKAGE_SHADOW
+	select BR2_PACKAGE_SHADOW_SUBORDINATE_IDS
+	select BR2_PACKAGE_SLIRP4NETNS  # runtime
+	help
+	  The best free & open source container tools
+
+	  Manage containers, pods, and images with Podman. Seamlessly
+	  work with containers and Kubernetes from your local
+	  environment.
+
+	  https://podman.io/
+
+if BR2_PACKAGE_PODMAN
+
+choice
+	bool "support podman-init"
+	default BR2_PACKAGE_PODMAN_INIT_CATATONIT
+	help
+	  Support providing a minimal init process for containers.
+	  Required to use "podman container run --init".
+
+config BR2_PACKAGE_PODMAN_INIT_NONE
+	bool "none"
+	help
+	  Do not support docker-init.
+
+config BR2_PACKAGE_PODMAN_INIT_CATATONIT
+	bool "catatonit"
+	select BR2_PACKAGE_CATATONIT # runtime
+	help
+	  Support providing a minimal init process for containers,
+	  using catatonit.
+
+config BR2_PACKAGE_PODMAN_INIT_TINI
+	bool "tini"
+	select BR2_PACKAGE_TINI # runtime
+	help
+	  Support providing a minimal init process for containers,
+	  using tini.
+
+endchoice
+
+config BR2_PACKAGE_PODMAN_INIT_NAME
+	string
+	default "tini" if BR2_PACKAGE_PODMAN_INIT_TINI
+	default "catatonit" if BR2_PACKAGE_PODMAN_INIT_CATATONIT
+
+endif
+
+comment "podman needs a toolchain w/ headers >= 3.17, threads, wchar"
+	depends on BR2_USE_MMU
+	depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS
+	depends on BR2_PACKAGE_HOST_RUSTC_TARGET_ARCH_SUPPORTS
+	depends on BR2_PACKAGE_LIBGPG_ERROR_ARCH_SUPPORTS
+	depends on BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS
+	depends on !BR2_TOOLCHAIN_HEADERS_AT_LEAST_3_17 \
+		|| !BR2_TOOLCHAIN_HAS_THREADS \
+		|| !BR2_USE_WCHAR

+ 2 - 0
package/podman/containers.conf

@@ -0,0 +1,2 @@
+[network]
+default_rootless_network_cmd = "slirp4netns"

+ 3 - 0
package/podman/podman.hash

@@ -0,0 +1,3 @@
+# Locally computed
+sha256  6c31845cbbc54a0b65c4afba224048c002b40e863c4ba7ca19355a8d333b3d19  podman-v5.4.1-git4-go2.tar.gz
+sha256  62fb8a3a9621dc2388174caaabe9c2317b694bb9a1d46c98bcf5655b68f51be3  LICENSE

+ 110 - 0
package/podman/podman.mk

@@ -0,0 +1,110 @@
+################################################################################
+#
+# podman
+#
+################################################################################
+
+PODMAN_VERSION = v5.4.1
+PODMAN_SITE = https://github.com/containers/podman
+PODMAN_SITE_METHOD = git
+
+PODMAN_LICENSE = Apache-2.0
+PODMAN_LICENSE_FILES = LICENSE
+
+PODMAN_DEPENDENCIES = host-pkgconf libgpgme
+
+PODMAN_GOMOD = github.com/containers/podman/v5
+PODMAN_BUILD_TARGETS = cmd/podman
+PODMAN_TAGS = selinux
+
+# https://podman.io/docs/installation#get-source-code mandates that flag be
+# set, as device-mapper is not officially supported.
+PODMAN_TAGS += exclude_graphdriver_devicemapper
+
+# This is supposedly optional, but a basic (busybox:latest) image does not
+# even start without seccomp support, unless by passing extra options at
+# runtime (--security-opt=seccomp=unconfined), which can't be made the default.
+PODMAN_DEPENDENCIES += libseccomp
+PODMAN_TAGS += seccomp
+
+# This is required for rootless containers, i.e containers started by non-root
+PODMAN_DEPENDENCIES += shadow
+PODMAN_TAGS += libsubid
+
+ifeq ($(BR2_PACKAGE_BTRFS_PROGS),y)
+PODMAN_DEPENDENCIES += btrfs-progs
+define PODMAN_LINUX_CONFIG_FIXUPS_BTRFS
+	$(call KCONFIG_ENABLE_OPT,CONFIG_BTRFS_FS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_BTRFS_FS_POSIX_ACL)
+endef
+else
+PODMAN_TAGS += exclude_graphdriver_btrfs
+endif
+
+ifeq ($(BR2_PACKAGE_LIBAPPARMOR),y)
+PODMAN_DEPENDENCIES += libapparmor
+PODMAN_TAGS += apparmor
+endif
+
+ifeq ($(BR2_PACKAGE_SYSTEMD),y)
+PODMAN_DEPENDENCIES += systemd
+PODMAN_TAGS += systemd
+endif
+
+PODMAN_INIT_NAME = $(call qstrip,$(BR2_PACKAGE_PODMAN_INIT_NAME))
+ifneq ($(PODMAN_INIT_NAME),)
+PODMAN_INIT_PATH = /usr/libexec/podman/$(PODMAN_INIT_NAME)
+define PODMAN_HELPER_INIT
+	$(Q)ln -sf ../../bin/$(PODMAN_INIT_NAME) $(TARGET_DIR)$(PODMAN_INIT_PATH)
+	$(Q)mkdir -p $(TARGET_DIR)/etc/containers/containers.conf.d
+	$(Q)printf '[containers]\ninit_path = "%s"\n' "$(PODMAN_INIT_PATH)" \
+		>$(TARGET_DIR)/etc/containers/containers.conf.d/50-buildroot-init.conf
+endef
+endif
+
+define PODMAN_LINUX_CONFIG_FIXUPS
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CPUSETS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_BPF_SYSCALL)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_POSIX_MQUEUE)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_MEMCG)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUPS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_SCHED)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_FREEZER)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_DEVICE)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_CPUACCT)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_PIDS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_CGROUP_BPF)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_NAMESPACES)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_UTS_NS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_IPC_NS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_PID_NS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_USER_NS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_NET_NS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_SECCOMP)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_OVERLAY_FS)
+	$(call KCONFIG_ENABLE_OPT,CONFIG_KEYS)
+	$(PODMAN_LINUX_CONFIG_FIXUPS_BTRFS)
+endef
+
+define PODMAN_CONFIG
+	$(Q)$(INSTALL) -D -m 0644 \
+		$(PODMAN_PKGDIR)/containers.conf \
+		$(TARGET_DIR)/usr/share/containers/containers.conf
+	$(Q)$(INSTALL) -D -m 0644 \
+		$(PODMAN_PKGDIR)/policy.json \
+		$(TARGET_DIR)/etc/containers/policy.json
+	$(Q)$(INSTALL) -D -m 0644 \
+		$(PODMAN_PKGDIR)/registries.conf \
+		$(TARGET_DIR)/etc/containers/registries.conf
+endef
+PODMAN_POST_INSTALL_TARGET_HOOKS += PODMAN_CONFIG
+
+define PODMAN_HELPERS
+	$(Q)mkdir -p $(TARGET_DIR)/usr/libexec/podman
+	$(Q)ln -sf ../../bin/netavark $(TARGET_DIR)/usr/libexec/podman/netavark
+	$(Q)ln -sf ../../bin/slirp4netns $(TARGET_DIR)/usr/libexec/podman/slirp4netns
+	$(PODMAN_HELPER_INIT)
+endef
+PODMAN_POST_INSTALL_TARGET_HOOKS += PODMAN_HELPERS
+
+$(eval $(golang-package))

+ 7 - 0
package/podman/policy.json

@@ -0,0 +1,7 @@
+{
+  "default": [
+    {
+      "type": "insecureAcceptAnything"
+    }
+  ]
+}

+ 1 - 0
package/podman/registries.conf

@@ -0,0 +1 @@
+unqualified-search-registries = ["docker.io"]

+ 203 - 0
support/testing/tests/package/test_podman.py

@@ -0,0 +1,203 @@
+import infra.basetest
+import json
+import os
+
+
+class PodmanBase(infra.basetest.BRTest):
+    config = \
+        """
+        BR2_arm=y
+        BR2_cortex_a9=y
+        BR2_ARM_ENABLE_VFP=y
+        BR2_TOOLCHAIN_EXTERNAL=y
+        BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
+        BR2_PER_PACKAGE_DIRECTORIES=y
+        BR2_SYSTEM_DHCP="eth0"
+        BR2_LINUX_KERNEL=y
+        BR2_LINUX_KERNEL_CUSTOM_VERSION=y
+        BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.10.202"
+        BR2_LINUX_KERNEL_DEFCONFIG="vexpress"
+        BR2_LINUX_KERNEL_DTS_SUPPORT=y
+        BR2_LINUX_KERNEL_INTREE_DTS_NAME="vexpress-v2p-ca9"
+        BR2_PACKAGE_PODMAN=y
+        BR2_PACKAGE_UTIL_LINUX=y
+        BR2_PACKAGE_UTIL_LINUX_MOUNT=y
+        BR2_PACKAGE_HOST_GO_BIN=y
+        BR2_TARGET_ROOTFS_EXT2=y
+        BR2_TARGET_ROOTFS_EXT2_SIZE="256M"
+        # BR2_TARGET_ROOTFS_TAR is not set
+        """
+
+    def do_test(self):
+        class _Emul():
+            def __init__(self, orig_emulator):
+                self.emulator = orig_emulator
+
+            def run(self, cmd, timeout=-1):
+                if timeout < 0:
+                    timeout = 60
+                return self.emulator.run(cmd, timeout)
+
+            def stop(self):
+                self.emulator.stop()
+
+        kernel_file = os.path.join(self.builddir, 'images', 'zImage')
+        dtb_file = os.path.join(self.builddir, 'images', 'vexpress-v2p-ca9.dtb')
+        ext2_file = os.path.join(self.builddir, 'images', 'rootfs.ext2')
+        self.emulator.boot(
+            arch='armv5',
+            kernel=kernel_file,
+            kernel_cmdline=[
+                'root=/dev/mmcblk0',
+                'rootwait',
+                'console=ttyAMA0',
+            ],
+            options=[
+                '-M', 'vexpress-a9',
+                '-dtb', dtb_file,
+                '-drive', f'file={ext2_file},if=sd,format=raw',
+            ]
+        )
+        self.emulator.login()
+
+        # Trick: replace the original emulator with one that always
+        # adds a timeout
+        self.emulator = _Emul(self.emulator)
+
+        # Do some preparation for rootless use
+        self.assertRunOk("mount --make-shared /")
+        self.assertRunOk("chmod 666 /dev/net/tun")
+        self.assertRunOk("useradd -d /home/foo -m -s /bin/sh -u 1000 foo")
+        self.assertRunOk("touch /etc/subuid /etc/subgid")
+        self.assertRunOk("usermod --add-subuids 10000-75535 foo")
+        self.assertRunOk("usermod --add-subgids 10000-75535 foo")
+        # If /etc/resolv.conf is a symlink, it has to point either into /etc
+        # (or deep in there), or into /run (or deep in there), as only those
+        # would eventually get used by podman/netavark for # rootless containers.
+        # This is considered a workaround to the current situation; resolv,conf
+        # should ultimately be in /run rather than /tmp.
+        self.assertRunOk("mv /tmp/resolv.conf /run/resolv.conf")
+        self.assertRunOk("ln -sf /run/resolv.conf /etc/resolv.conf")
+
+        # First, test podman as root (the current user)
+        self.do_podman()
+
+        # Now, test podman as non-root. We need a bit of setup
+        # We need to use the same prompts for the user as used for root, so that the
+        # REPLWrapper still detects the prompts. This means it is going to be a bit
+        # difficut to directly see that it was a user that executed a command.
+        self.assertRunOk('su -s /usr/bin/env - foo PS1="${PS1}" PS2="${PS2}" /bin/sh')
+        output, _ = self.emulator.run("id -u")
+        self.assertEqual(output[0], "1000", "Could not switch to non-root")
+        self.do_podman()
+
+    def do_podman(self):
+        # The podman binary is huge, so it takes time to load...
+        # Next calls will be faster, though, as it is going to be cached.
+        self.assertRunOk('podman --version')
+
+        # Check for an empty image store
+        output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'")
+        img_info = json.loads("".join(output))
+        self.assertEqual(len(img_info), 0, f"{len(img_info)} image(s) already present")
+
+        # Pull an image; it can take time: network, hash checksums...
+        self.assertRunOk('podman image pull busybox:1.37.0')
+        output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'")
+        img_info = json.loads("".join(output))
+        self.assertEqual(len(img_info), 1, f"{len(img_info)} image(s), expecting 1")
+        self.assertTrue("Id" in img_info[0], '"Id" not in img_info[0]')
+        self.assertTrue("Digest" in img_info[0], '"Digest" not in img_info[0]')
+        self.assertEqual(img_info[0]["Names"][0], "docker.io/library/busybox:1.37.0")
+
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "", "Already in a container")
+
+        # Spawn the container; that can take a bit of time
+        # Propagate the prompt so that the REPLWrapper detects it
+        self.assertRunOk(
+            "podman container run --rm -ti -e PS1 -e br_container=podman busybox:1.37.0",
+        )
+        # Twist! The command above is still running, but the shell it
+        # started exposes the same prompt we expect. This is all what we want.
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "podman", "Not in a podman container")
+
+        # Check that pid1 is the shell
+        output, _ = self.emulator.run('readlink /proc/1/exe')
+        self.assertEqual(output[0], "/bin/sh", f"PID1 is {output[0]}, should be /bin/sh")
+
+        # Try to get something off the network
+        # Using http, not https, as busybox' wget does not do https
+        # Using --spider to just check we can reach the remote.
+        output, exit_code = self.emulator.run('wget --spider http://google.com/')
+        self.assertEqual(exit_code, 0, "wget did not succeed to reach google.com")
+        self.assertEqual(output[-1], "remote file exists", "wget did not succeed to reach google.com")
+
+        # Exit the container
+        self.assertRunOk("exit 0")
+        # Twist, take two! We are now back to the shell in the VM.
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "", "Still in a container")
+
+        # Spawn a container, round two, but with an injected init this time
+        self.assertRunOk(
+            "podman container run --rm -ti -e PS1 --init -e br_container=podman busybox:1.37.0",
+        )
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "podman", "Not in a podman container")
+
+        # Check that pid1 is the init injected by podman
+        output, _ = self.emulator.run('readlink /proc/1/exe')
+        self.assertEqual(output[0], "/run/podman-init", f"PID1 is {output[0]}, should be /run/podman-init")
+
+        # Exit the container
+        self.assertRunOk("exit 0")
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "", "Still in a container")
+
+        # Use an image from another registry, spawn without pulling first
+        self.assertRunOk(
+            "podman container run --rm -ti -e PS1 -e br_container=podman quay.io/prometheus/busybox:latest",
+        )
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "podman", "Not in a podman container")
+        self.assertRunOk("exit 0")
+        output, _ = self.emulator.run('echo ${br_container}')
+        self.assertEqual(output[0], "", "Still in a container")
+
+        # Remove the offical image
+        self.assertRunOk('podman image rm busybox:1.37.0')
+        output, _ = self.emulator.run("podman image ls --format '{{ json }}'")
+        img_info = json.loads("".join(output))
+        # There is still one image(the unofficial one from quay.io)
+        self.assertEqual(len(img_info), 1, f"{len(img_info)} image(s) still present, expecting 1")
+
+        # Remove all remaining images
+        self.assertRunOk('podman image prune -af')
+        output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'")
+        img_info = json.loads("".join(output))
+        self.assertEqual(len(img_info), 0, f"{len(img_info)} image(s) still present, expecting 0")
+
+
+class TestPodmanIptables(PodmanBase):
+    def test_run(self):
+        self.do_test()
+
+
+class TestPodmanNftables(PodmanBase):
+    config = PodmanBase.config + """
+    BR2_PACKAGE_NFTABLES=y
+    """
+
+    def test_run(self):
+        self.do_test()
+
+
+class TestPodmanTini(PodmanBase):
+    config = PodmanBase.config + """
+    BR2_PACKAGE_PODMAN_INIT_TINI=y
+    """
+
+    def test_run(self):
+        self.do_test()