Getting Started With Rootless Container Using Podman
The advantages of a rootless container are obvious. Learn how to use rootless containers with Podman in this tutorial.
Are you deploying software using containers? Are you using Podman? Do you want to up your security game by running containers with as little privilege as possible? Boy, do I have an article for you!
What is Podman?
Podman is a Red Hat product aimed as a replacement for Docker. For 99% of tasks, it is indeed a true Docker replacement. A few of its features are support for root-less containers, uses the fork/exec model to start containers, is daemon-less, and more.
The advantages of a rootless container are obvious. If it can be prevented from running as root, you run it without root privileges.
With this article, I hope to help remove some hurdles that may crop up when you use Podman to deploy rootless containers.
Podman in rootless execution
If you are a seasoned IT professional, you might have committed either one of the following crimes:
- Running the
docker
command usingsudo
, escalating its privileges - Adding your user non-root user to the
docker
group. big oof
As you might have realized by now, this is a terrible security practice. You are giving the Docker daemon root access to your machine. That exposes two methods of exploitation:
- The Docker daemon (
dockerd
) runs as root. Ifdockerd
has a security vulnerability, your entire system is compromised becausedockerd
is a process owned by theroot
user. - An image that you use might have vulnerabilities. What if the vulnerable imgae is used by a container that is running as a process of the
root
user? An attacker can use the vulnerable image to gain access to your entire system.
The solution is simple, don't run everything as root, even if you trust it. Remember, nothing is 100% secure. I present to you Podman's ability to manage containers without root access.
If you start a container using Podman as a non-root user, said container does not gain any additional privileges, nor will Podman ask you for a sudo
password.
Below are the benefits Podman provides when you use it for root-less containers (without any super-user privileges):
- You can isolate a group of common containers per local user. (e.g., run Nextcloud and MariaDB under user
nextcloud_user
and containers Gitea and PostgreSQL under the usergitea_user
) - Even if a container/Podman gets compromised, it can not get complete control over the host system, since the user executing the container is not
root
. But yes, the user under which the exploited container is running might as well be considered as user gone rogue.
Limits of root-less Podman
When you use root-full Podman/Docker, you are giving Podman/Docker super-user level privileges. That is certainly very bad, but it also means that all of the advertised functionalities work as intended.
Instead, when you run Podman containers without root privileges, it has some limits. Some of the major ones are as follows:
- Container images can not be shared across users. If
user0
pulls the 'nginx:stable-alpine
' image,user1
will have to separately pull the 'nginx:stable-alpine
' image for themselves. There is no way [at least not yet] that allows you to share images between users. But, you can copy images from one user to another user, refer to this guide by Red Hat. - Ports less than 1024 cannot be binded out of the box. A workaround exists.
- A root-less container may not be able to ping any hosts. A workaround exists.
- If you specify a UID in root-less Podman container, any UID that is not mapped to a pre-existing container may fail. Best to execute Podman from an existing user shell. Or better yet, create a systemd service to auto-start it.
Getting started with root-less Podman
Before you get started with the rootless execution of containers, there are a few prerequisites that need to be met.
Make sure you have slirp4netns
installed
The slirp4netns
package is used to provide user-mode networking for unprivileged network namespaces. This is a necessary if you want your root-less container to interact with any kind of network.
You can install the slirp4netns
package on Debian/Ubuntu based Linux distributions using the apt
package manager like so:
sudo apt install slirp4netns
On Fedora/RHEL based Linux distributions, use the dnf
package manager to install slirp4netns
like so:
sudo dnf install slirp4netns
You Arch Linux users know how to do it with pacman
, but regardless, below is the command you might be looking for:
sudo pacman -Sy slirp4netns
Make sure that your subuid
and subgid
are properly configured
Since root-less Podman containers are run by an existing user on the system, said non-root users need permission to run a root-less container as a UID that is not their own UID. This also applies to the GID.
Each user is given a range of UIDs that it is allowed to use. This is specified in the /etc/subuid
file; and the /etc/subgid
file is for the GIDs a user is allowed to use.
The format of this file is as following:
username:initial UID/GID allocated to user:range/size of allowd UIDs/GIDs
So, let us say my user, pratham
wants 100 UIDs for himself and krishna
wants 1000 UIDs for himself. Below is how the /etc/subuid
file would look like:
pratham:100000:100
krishna:100100:1000
What this effectively means is that the user pratham
can use UIDs between '100000' and '100100'. Meanwhile, user krishna
can use UIDs between '100100' and '101100'.
Usually, this is already set up for each user you create. And usually, this range is set to '65536' usable GIDs/UIDs. But in some cases, this needs to be done manually.
But hold on, if this is not already done for your user, you do not need to do this by hand for each user. You can use the usermod command for this. Below is the command syntax to do so:
sudo usermod --add-subuids START-RANGE --add-subgids START-RANGE USERNAME
Replace the strings START
, RANGE
and USERNAME
according to your needs.
/etc/subuid
and /etc/subgid
are set to 644
and is owned by root:root
.Want to bind Ports less than 1024?
If you are using a reverse proxy for SSL, you will know that ports 80 and 443 need to be accessible by a certificate provider like Let's Encrypt.
If you try to bind ports lower than 1024 to a root-less container managed by Podman, you will notice that it is not possible. Well, it is possible, but that is not configured out of the box.
A non-root user is not allowed to bind anything on ports less than port 1024.
So, how do I bind ports lower than 1024 in root-less Podman? To do that, first determine the lowest port that you need. In my case, to deploy SSL, I need ports 80 and 443. So the lowest port that I need is port 80.
Once that is determined, add the following line to the /etc/sysctl.conf
file:
net.ipv4.ip_unprivileged_port_start=YOUR_PORT_NUMBER
Essentially, you are changing the value of net.ipv4.ip_unprivileged_port_start
to the lowest port you need. If I substitute YOUR_PORT_NUMBER
with 80, I can bind port 80 with Podman in a root-less container.
WHERE ARE MY IMAGES?
No need to scream at me buddy. I was going to tell you, eventually...
As I pointed out earlier, a limitation of Podman is that it can not share images between users. They either need to be pulled for each user or be copied from one user to another user. Both of these take up 2x/3x/4x space (depending on how many duplicates exist).
The image for any user is stored in their home directory. Specifically, they are stored inside the ~/.local/share/containers/storage/
directory.
Give it a try!
Once all the prerequisites are satisfied, you can run the podman run
command from a non-user's shell and start a container.
Since I use Caddy Server for my SSL, I will use that in this tutorial. To run Caddy Server as a root-less container using Podman, while binding it ports lower than 1024, I will simply run the following command:
$ whoami
pratham
$ podman run -d --name=prathams-caddy -p 80:80 -p 443:443 caddy:alpine
e6ed67eb90e6d0f3475d78b287af941bc873f6d62db60d5c13b1106af80dc5ff
$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6ed67eb90e6 docker.io/library/caddy:alpine caddy run --confi... 2 seconds ago Up 2 seconds ago 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp prathams-caddy
$ ps aux | grep caddy
pratham 3022 0.0 0.0 85672 2140 ? Ssl 06:53 0:00 /usr/bin/conmon --api-version 1 -c e6ed67eb90e6d0f3475d78b287af941bc873f6d62db60d5c13b1106af80dc5ff [...]
pratham 3025 0.1 0.3 753060 32320 ? Ssl 06:53 0:00 caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
As you can see, the user pratham
is not root
and also that I did not use the sudo
command to escalate privileges of the user pratham
. I was able to run the Caddy Server container with root-less privileges using Podman.
The output of ps
command shows that the PID 3022
is of a process owned by the pratham
user. This process is the Caddy Server container (I have trimmed the output). The PID 3025
is a child process of PID 3022
which is also under the pratham
user.
Did I not address your issue?
I apologize if I did not cover your issue with root-less Podman containers. Since Podman is new (in software years), it will have some unexpected issues.
Fret not. I have included the official troubleshooting guide below. Refer to that when in doubt. It is also the one that will be updated frequently.
Conclusion
In this article, I demonstrate how you can start managing rootless containers using Podman. I talk about the necessary software that enables this functionality. I go over common issues you may face with root-less Podman containers and how to mitigate them.
I hope it helps you and if you need more details, check the official documentation where you might get a more technical troubleshooting guide.
Navigating through the world of Rust, RISC-V, Podman. Learning by doing it and sharing by writing it.