Podman is the best non-root Docker tool I’ve found. Let me show you why.

Podman for Non-Root Docker

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.