When a Linux container image is created, like any system it can have specific users, or maybe it only has
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
# 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
# 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
# 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
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
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/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
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
$ id -u 1000
Looking at the
subuid file we can see that my user has a range of
65536 subuids starting from
$ cat /etc/subuid csmart:100000:65536
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
$ 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
$ 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…