Podman for Non-Root Docker
Podman is the best non-root Docker tool I’ve found. Let me show you why.
After writing about the HPC Containers Survey, I started thinking more about the survey results and how I use containers. From the survey, I noticed that people:
- use Docker on their local machines quite a bit relative to other technologies, even though the systems they use run Singularity;
- use the Docker recipe more than Singularity; and
- use DockerHub as a central repository.
My primary takeaway was that people use Docker quite a bit even with its root issues, so I wanted to find out whether I could employ one of the techniques for using Docker without root.
Rootless Docker
Rootless Docker is basically a technique for running the Docker daemon as a non-root user; this method is applied on a per-user basis, which means each user has to run their own daemon. Although you can find several installation directions to achieve this end, after following the “official” guide for rootless Docker, I was able to pull containers, run them, and create new containers with a Dockerfile.
The next test was to create a new user and follow the same instructions for rootless Docker. However, I could not get basic tasks to work, such as pulling a container. I tried several things to correct this problem:
- uninstalling and re-installing, and I still could not get Docker to work with the user;
- uninstalling again and trying some other instructions that were basically the same, which didn’t work either;
- deleting the user and creating a completely new one, then installing rootless Docker for this user. Again, I could get nothing to work.
At this point, I punted. It was great that I could run Docker as a non-root user (my account), but if I could not get another user using rootless Docker, I decided it was not worth the effort.
udocker
Another tool that allows you to execute (run) Docker containers without requiring root is udocker. In reading through the documentation, I didn’t see any way to get udocker to build containers (i.e., docker build …) or create images from running containers (docker commit …), which are two things I do regularly. However, in the interest of testing, I thought I would give it a whirl.
The installation instructions helped me install udocker quickly and easily; however, it ends there. I could not run any Docker commands with the instructions provided. I kept getting errors.
I wanted to make sure I tried using udocker as a new user. I followed the udocker installation instructions for this new user. The same thing happened – I could not pull any Docker containers.
At this point, I was getting paranoid that I could no longer install tools following README instructions and use the installed code. I scoured the system to purge absolutely any sign of Docker. I ran lots of different uninstall instructions, and nothing changed (i.e., nothing was uninstalled nor were any remnants found). I felt confident that I didn’t have any remnants of either Docker, rootless Docker, or udocker on the system, which led me to believe I had correctly uninstalled them during testing (i.e., I did not corrupt or damage any installation from installing previous tools).
Podman to the Rescue
At this point in my search, I was ready to give up, but I remembered that Podman is supposed to be Docker compatible, so I thought I would give it a try. I followed some instructions for installing Podman on Ubuntu 24.04 and learned it was dead simple to install, even for me (Listing 1). I spared you the gory details of the installation, which went fine.
Listing 1: Installing Podman
$ sudo apt-get update $ sudo apt-get -y install podman Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: aardvark-dns buildah catatonit conmon containernetworking-plugins crun golang-github-containers-common golang-github-containers-image netavark passt Suggested packages: containers-storage libwasmedge0 docker-compose The following NEW packages will be installed: aardvark-dns buildah catatonit conmon containernetworking-plugins crun golang-github-containers-common golang-github-containers-image netavark passt podman 0 upgraded, 11 newly installed, 0 to remove and 0 not upgraded. Need to get 32.3 MB of archives. After this operation, 131 MB of additional disk space will be used. ... Processing triggers for man-db (2.12.0-4build2) ...
In reading through the Podman manual, it appears that almost everything I do with Docker commands are the same in Podman, except for the first command. I just changed the use of the command docker … to podman …. Very nice, but I’m not a power Docker user, so I’m sure I missed things with this simple substitution.
Pulling and Running
Of course, one of the first things I tried to do was pull a container. For the sake of argument, I pulled the latest Ubuntu container (Listing 2). The output looked good so far. Note that I checked whether it was downloaded with the command podman images, but I didn’t capture the output. It was there.
Listing 2: Pulling Ubuntu Image
$ podman pull ubuntu Resolved "ubuntu" as an alias (/etc/containers/registries.conf.d/shortnames.conf) Trying to pull docker.io/library/ubuntu:latest... Getting image source signatures Copying blob 9c704ecd0c69 done | Copying config 35a8880255 done | Writing manifest to image destination 35a88802559dd2077e584394471ddaa1a2c5bfd16893b829ea57619301eb3908
Then it was time to run the container (Listing 3). Notice that I checked the /etc/os-release file to make sure it was, in fact, Ubuntu. I was a little paranoid because the system used Ubuntu as the base OS, so I pulled a CentOS container and checked that /etc/os-release file. Everything matched (I didn’t capture the output).
Listing 3: Running the Container
$ podman run -it ubuntu /bin/bash root@bc9e96775b5a:/# cat /etc/os-release PRETTY_NAME="Ubuntu 24.04 LTS" NAME="Ubuntu" VERSION_ID="24.04" VERSION="24.04 LTS (Noble Numbat)" VERSION_CODENAME=noble ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=noble LOGO=ubuntu-logo
Because I had issues with a new user with the previous tools, I created yet another new user and SSH’d into the account on the system. Without installing anything, I pulled and ran an alpine container (Listing 4). Notice that Alpine Linux does not have Bash, so you have to use the sh shell. However, the content of /etc/os-release matched the container.
Listing 4: Creating Alpine Container
testuser@laytonjb-MINI-S:~$ podman images REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/library/hello-world latest d2c94e258dcb 15 months ago 28.5 kB quay.io/centos/centos latest 300e315adb2f 3 years ago 217 MB testuser@laytonjb-MINI-S:~$ podman pull alpine Resolved "alpine" as an alias (/etc/containers/registries.conf.d/shortnames.conf) Trying to pull docker.io/library/alpine:latest... Getting image source signatures Copying blob c6a83fedfae6 done | Copying config 324bc02ae1 done | Writing manifest to image destination 324bc02ae1231fd9255658c128086395d3fa0aedd5a41ab6b034fd649d1a9260 testuser@laytonjb-MINI-S:~$ podman run -it alpine /bin/sh / # cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.20.2 PRETTY_NAME="Alpine Linux v3.20" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" / #
Building a Container Image
One common task is to build containers from spec (specification) files, which for Docker containers are usually called Dockerfiles (but they don’t have to be). For a demonstration, I used the Ubuntu container, and when I ran the container, I checked that GCC and GFortran were installed:
$ podman run -it ubuntu /bin/bash root@9e3e2c0d63ba:/# ls -s /usr/bin/gcc ls: cannot access '/usr/bin/gcc': No such file or directory root@9e3e2c0d63ba:/# ls -s /usr/bin/gfortran ls: cannot access '/usr/bin/gfortran': No such file or directory root@9e3e2c0d63ba:/#
That output indicates that neither is installed in the container. As an experiment, I decided to build a container that used the Ubuntu container as a base but also had GCC installed (GFortran comes later). The Dockerfile to do so is very simple:
FROM ubuntu RUN apt-get update; apt-get install -y gcc-11
The command to build the container image, as well as a portion of the output from the build process, is shown in Listing 5. Note that I created the new ubuntu-dev container image (Listing 6). The size of the container relative to the base container, ubuntu, changed significantly just by adding in the GCC compiler.
Listing 5: Build Process
$ podman build -t ubuntu-dev -f Dockerfile . STEP 1/2: FROM ubuntu STEP 2/2: RUN apt-get update; apt-get install -y gcc-11 Get:1 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB] Get:2 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB] Get:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB] Get:4 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Packages [319 kB] Get:5 http://archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB] Get:6 http://archive.ubuntu.com/ubuntu noble/restricted amd64 Packages [117 kB] Get:7 http://archive.ubuntu.com/ubuntu noble/multiverse amd64 Packages [331 kB] Get:8 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [256 kB] Get:9 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Packages [12.7 kB] Get:10 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [316 kB] Get:11 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages [19.3 MB] Get:12 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages [1808 kB] Get:13 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [405 kB] Get:14 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [373 kB] Get:15 http://archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Packages [16.9 kB] Get:16 http://archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [256 kB] Get:17 http://archive.ubuntu.com/ubuntu noble-backports/universe amd64 Packages [11.5 kB] Fetched 24.2 MB in 2s (13.1 MB/s) Reading package lists... Reading package lists... ... Processing triggers for libc-bin (2.39-0ubuntu8.2) ... COMMIT ubuntu-dev --> f12343192636 Successfully tagged localhost/ubuntu-dev:latest f1234319263614bdb6e6aca8f181a79c352cdca6df213b143accbc606ce95902
Listing 6: podman images
$ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/ubuntu-dev latest f12343192636 38 seconds ago 303 MB docker.io/library/alpine latest 324bc02ae123 4 days ago 8.09 MB docker.io/library/ubuntu latest 35a88802559d 7 weeks ago 80.6 MB docker.io/library/hello-world latest d2c94e258dcb 15 months ago 28.5 kB
Finally, to check the container, I ran it and tried gcc (Listing 7).
Listing 7: Checking the ubuntu-dev Container
root@eb4862944445:/# gcc-11 -v Using built-in specs. COLLECT_GCC=gcc-11 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-9ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2 Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.4.0 (Ubuntu 11.4.0-9ubuntu1)
One other Docker feature frequently used is installing packages into the container and saving the running container as an image for later use. As a test, I ran the ubuntu-dev container that had GCC installed (I will save you from looking at the output); once I was in the container, I ran the following two commands:
root@eb4862944445:/# apt-get update root@eb4862944445:/# apt-get install -y gfortran-11
I also checked for a GFortran version to make sure it was there (Listing 8). It’s there, too (all good).
Listing 8: Checking for GFortran
root@eb4862944445:/# /usr/bin/gfortran-11 -v Using built-in specs. COLLECT_GCC=/usr/bin/gfortran-11 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-9ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2 Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.4.0 (Ubuntu 11.4.0-9ubuntu1)
The task of saving the running container to a new image is not difficult with the podman commit directive. Start by leaving the container running and going to a new terminal window; then, get a list of all containers and images with the directive (Listing 9).
Listing 9: podman ps -a
$ podman ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc9e96775b5a docker.io/library/ubuntu:latest /bin/bash 4 hours ago Exited (127) 4 hours ago infallible_cartwright ecd005537914 docker.io/library/hello-world:latest /hello 4 hours ago Exited (0) 4 hours ago blissful_dubinsky 96f0c374d79f docker.io/library/alpine:latest /bin/sh 4 hours ago Exited (0) 4 hours ago infallible_knuth 7f2d5a7b7c86 docker.io/library/ubuntu:latest /bin/bash 24 minutes ago Exited (127) 22 minutes ago wonderful_sinoussi 85b8e9efc636 docker.io/library/ubuntu:latest /bin/bash 19 minutes ago Exited (0) 19 minutes ago xenodochial_allen 9e3e2c0d63ba docker.io/library/ubuntu:latest /bin/bash 19 minutes ago Exited (2) 18 minutes ago stupefied_lumiere 447c39c967c7 docker.io/library/ubuntu:latest /bin/bash 5 minutes ago Exited (1) 4 minutes ago funny_pasteur eb4862944445 localhost/ubuntu-dev:latest ./bin/bash 2 minutes ago Up 2 minutes eloquent_poitras
The container I’m currently running is ubuntu-dev. I copied the CONTAINER ID for that container and used it with commit to save it to a new image (Listing 10). Note that I named this new image ubuntu-dev2.
Listing 10: podman commit
$ podman commit eb4862944445 ubuntu-dev2 Getting image source signatures Copying blob a30a5965a4f7 skipped: already exists Copying blob fa7c69d63417 skipped: already exists Copying blob eaec3bafbdf2 done | Copying config 8948ad0c2d done | Writing manifest to image destination 8948ad0c2df0629855351037dddc35fbd39bea6bc8a65ff18937889550356af8
To see if the new container is there, enter podman images (Listing 11), and to check for GFortran, run the container and check the version (Listing 12). Success!
Listing 11: podman images
$ podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/ubuntu-dev2 latest 8948ad0c2df0 27 seconds ago 343 MB localhost/ubuntu-dev latest f12343192636 5 minutes ago 303 MB docker.io/library/alpine latest 324bc02ae123 4 days ago 8.09 MB docker.io/library/ubuntu latest 35a88802559d 7 weeks ago 80.6 MB docker.io/library/hello-world latest d2c94e258dcb 15 months ago 28.5 kB
Listing 12: Checking for GFortran
$ podman run -it ubuntu-dev2 /bin/bash root@235adef577a1:/# /usr/bin/gfortran-11 -v Using built-in specs. COLLECT_GCC=/usr/bin/gfortran-11 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-9ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-ZcnBzW/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2 Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.4.0 (Ubuntu 11.4.0-9ubuntu1)
Summary
After looking at the HPC Container Survey results, I was intrigued by how people use container tools and which ones they used. That started me thinking about my container practices. I really wanted to get away from the need for root access to run and create Docker containers, which led me to try both rootless Docker and udocker. I tried both and really couldn’t get either one to work fully, especially with other users.
These failures led me to Podman, which impressed me, to say the least. For my container practices and habits, it worked perfectly. I just changed the docker … commands to podman … commands. It was that simple. I did not need root access at all. It worked out of the gate for a new user, as well.
Podman is relatively new compared with Docker, so I’m sure it either won’t work or will require some workarounds in corner cases. Some articles floating around claim Podman’s CLI is more complex than Docker’s, although I didn’t encounter any issues in using it, but I’m sure I’m just a “plane Jane” container user, so I didn’t run into any issues.