User IDs and (rootless) containers with Podman

When a Linux container image is created, like any system it can have specific users, or maybe it only has root (uid 0). Containers have a specific entry point which runs the program the image was created for and this might run as any user in the image, it’s up to whoever created it.

You can use podman (a daemonless container engine) to easily see what uid an image will use, by getting the container to run the id command instead of the default entry point.

For example, here we can see that busybox wants to run as root (uid 0).

# podman run --rm --entrypoint '' docker.io/busybox id
uid=0(root) gid=0(root) groups=0(root)

However, grafana wants to run as the grafana user with uid 472 .

# podman run --rm --entrypoint '' docker.io/grafana/grafana id
uid=472(grafana) gid=0(root) groups=0(root)

OK, so inside the containers we are running as different users, but as we’re running as root those same uids are also used on the host system.

Running containers as root

Let’s run a grafana container as root and see that the actual grafana-server process on the host is running under uid 472.

# podman run -d docker.io/grafana/grafana
ee275572c8dcb0922722b7d6c3e5ff0bda8c9af3682018c4fc53675d8e189e59
# ps -o user $(pidof grafana-server)
USER
472

Now, remember how busybox wanted to run as uid 0? Let’s run top in the busybox container and see that the process does indeed run as root on the host.

# podman run -d docker.io/busybox top
478a50a9054b36fc1e1c0f0dc005ae4393d60ecbbd6ba2bf5021b255c5d3d133
# ps -o user $(pidof top)
USER
root

So, running a container as root will use whatever uid is inside the container to run its process on the host. This might “conflict” with other users already on the system for example, if 472 already exists. Furthermore, as with any process on a host, it’s probably not ideal to run it as root.

We can, however override the uid that’s used in the container with the --user option. For example, here I’m telling the container to run as uid 1000 which means the top process will actually run as my non-root csmart user on the host.

# podman run --user 1000:1000 -d docker.io/busybox top
699449a882b0a6402728176f2773bc87d55ada8115ef55eeca2cba465a70a018
# ps -o user $(pidof top)
USER
csmart

While we can run containers as root and have its process execute as a non-root user on the host (which is good), there are still a few downsides. For example, it requires root access in the first place, parts of the container (such as conmon) are still running as root and a vulnerability somewhere in the stack might render the user protection useless.

Running rootless containers

As with any Linux process, it’s safer if we can run a container as a non-root user.

When running the container as a non-root user however, how do we run as root (uid 0) when you aren’t root on the host? We need a way to allow the container inside to be root, but not on the actual host system running it.

Fortunately this is possible and managed with rootless containers via /etc/subuid and /etc/subgid config files. This sets different uid and gid range offsets for each user, so while multiple users might run the same container with the same internal uid, it will get translated to a different uid on the host, thus avoiding conflicts.

Running rootless containers as root, as you

One of the neat things this does is to map root (uid 0) in the container to your non-root user on the host. This way when a container runs as root, it’s actually running as you!

Let’s take a look. My current non-root user on my host is csmart which has a uid of 1000.

$ id -u
1000

Looking at the subuid file we can see that my user has a range of 65536 subuids starting from 100000.

$ cat /etc/subuid
csmart:100000:65536

Using the podman unshare command (which runs under a new user namespace) we can confirm the user range is applied for our user.

$ podman unshare cat /proc/self/uid_map
0 1000 1
1 100000 65536

OK, but what does that mean? Well, here we can see that for my user, the root account with uid 0 in a container actually maps to the 1000 uid of our non-root user on the host.

Then, the uid of 1 in a container for my user would map to 100000 on the host, 2 would be 100001 and so on. To use grafana as an example, running it in a rootless container with uid of 472 would map to 100471 on the host for my user.

We can also use the id command, along with podman unshare again, to compare our uid outside a container (as non-root user) and inside a container (as root).

$ id
uid=1000(csmart) gid=1000(csmart) groups=1000(csmart)
$ podman unshare id
uid=0(root) gid=0(root) groups=0(root)

Great. So running a container with user root (uid 0) will translate to our non-root user on the host (uid 1000 in this case).

So, let’s see what happens when my non-root user runs the top command in a busybox container (remember busybox runs as root inside the container, but I’m running it as my non-root user 1000 on the host).

$ podman run -d --name busybox docker.io/busybox top
72a40c16be71020b0f4be6af447c55b32a6fd406a97d4861bbf2794a50b04a5d
$ podman exec -it busybox id
uid=0(root) gid=0(root)
$ ps -o user $(pidof top)
USER
csmart

So, if you have a container that wants to run as root, this will automatically be translated to your regular non-root user on the host.

However, if the container you’re running uses as a different uid (such as grafana with uid 472), then you can always pass in the option --user 0:0 to make it run as root inside the container (which then runs as your non-root user on the host).

$ podman run --user 0:0 -d --name grafana docker.io/grafana/grafana
d44b5e61d856e585c57ab0922d8f19f7d7eeed6f9a7fbabb149bb344fe20f955
$ podman exec -it grafana id
uid=0(root) gid=0(root)
$ ps -o user $(pidof grafana-server)
USER
csmart

Running rootless containers as other uids, as you

Remember though, that you don’t have to run the container as root and have it translate to your own user, you have a full 65536 users you can run as! This way you can also achieve some isolation between your own containers, while still taking advantage of rootless containers.

OK, so what if you have a container that does not use root, what happens? Easy! The uid gets mapped to the offsets in the subuid file. Remember that grafana wants to run as uid 472? Well this will translate to 100471 on the host for my user (uid 1000).

$ podman run -d docker.io/grafana/grafana
541bef2aea52fa2a45e440bc5bf1d13797b83750405a794c2153af3e61580040
$ ps -o user $(pidof grafana-server)
USER
100471

So, we can run lots of containers as different users and keep them separate both from each other and from any other users’ containers, all without needing root on the host. Great!

Again, you can manage which user each container runs as, by passing in the --user option to podman and have it map to your own subuid space.

Running multiple containers works well, but it does get more tricky if you need to pass in directories on the host for persistent storage. That will probably be the topic of another post…

Leave a Reply

Your email address will not be published. Required fields are marked *