# Distrobox: `sudo` without `sudo` > Author: Sasank Chilamkurthy Distrobox is a super flexible tool that comes preinstalled in JOHNAIC. It allows you to setup different Linux distributions and create your own development environment. The killer feature is the ability to do `sudo` even if your account doesn't have it. This creates a perfect experience for a developer where they feel *both* powerful and safe. In this post, I'll quickly explain how distrobox works followed by a quick hands on. ## How it works In [previous](https://von-neumann.ai/blog/security-virtualization-containerization.html) [posts](https://von-neumann.ai/blog/security-rootless-containers.html), we discussed ways to balance user power and protection of other users from this power. Virtual machines and containers were presented as solutions to this problem. Industry moved from VMs to containers because containers are both light weight and secure. We saw that rootless containers from podman further improve the security posture. By default, containers present independence from the host system. They have their own filesystem and network separate from host system. You need to publish ports and define mounts if you want to expose a part of the host system to the container. Let's see this in action. Here are some of the files in my downloads folder in host system. ``` sasank@JOHNAIC:~$ ls ~/Downloads/ Miniconda3-py311_24.7.1-0-Linux-x86_64.sh ``` Let's run a container in another terminal ``` sasank@JOHNAIC:~$ podman run -it --rm ubuntu bash root@ca3fec3a2731:/# ls ~/Downloads/ ls: cannot access '/root/Downloads/': No such file or directory ``` Note how your files are not available inside the container. Similarly network is isolated by default. Install python inside container and run a quick file server. ``` root@675d674baf59:/# apt update && apt install -y python3 root@675d674baf59:/# python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... ``` Now in the host system, try accessing this http server using curl. Your connection will be refused. Thus network is isolated as well. ``` sasank@JOHNAIC:~$ curl 0.0.0.0:8000 curl: (7) Failed to connect to 0.0.0.0 port 8000 after 0 ms: Connection refused ``` This sort of isolation is very useful when you want to run multiple applications in their own sandboxes independent of each other. However, this also becomes very restrictive for a user on the machine if he's using container as a sort of OS for his development. He should be able to access his home folder and expose whatever ports he can. Thus we have distrobox with tight integration of containers with the host. Distrobox creates a user with the same username as you inside the container, mounts your home folder into it, binds all non-privileged container ports to the host ports. The root directory remains isolated inside the container. This creates an amazing experience for the user where he can run `sudo` inside a distrobox to install his packages. His home folder and code in it will be shared across different distroboxes he uses. Distrobox uses a container engine to achieve all this -- either docker or podman. In JOHNAIC, distrobox is configured to work with rootless podman. This means that users themselves are sandboxed between each other thus ensuring security. Anyway, let's get to hands on! ## Hands on Here's how you create a new ubuntu distrobox ``` sasank@JOHNAIC:~$ distrobox create --name my-distro --image ubuntu:22.04 --additional-flags "--device nvidia.com/gpu=all" Creating 'my-distro' using image ubuntu:22.04 [ OK ] Distrobox 'my-distro' successfully created. To enter, run: distrobox enter my-distro ``` Let's break down what's going on here. * `create` asks distrobox to create a new distrobox * `--name my-distro` set the name of this box to my-distro * `--image ubuntu:22.04` asks distrobox to use ubuntu 22.04 as the base distribution. You can use many other distros like Debian, Fedora and openSUSE etc. * `--additional-flags "--device nvidia.com/gpu=all"` makes nvidia gpu available inside the distrobox Now let's enter the newly minted distrobox. When you enter a distrobox the first time, it sets it up with basic packages etc. This might take a while but it happens only once. ``` sasank@JOHNAIC:~$ distrobox enter my-distro Starting container... [ OK ] Installing basic packages... [ OK ] Setting up devpts mounts... [ OK ] Setting up read-only mounts... [ OK ] Setting up read-write mounts... [ OK ] Setting up host's sockets integration... [ OK ] Integrating host's themes, icons, fonts... [ OK ] Setting up package manager exceptions... [ OK ] Setting up dpkg exceptions... [ OK ] Setting up apt hooks... [ OK ] Setting up distrobox profile... [ OK ] Setting up sudo... [ OK ] Setting up groups... [ OK ] Setting up users... [ OK ] Setting up skel... [ OK ] Container Setup Complete! sasank@my-distro:~$ ``` Note how my prompt changed to `sasank@my-distro`! I am now inside distrobox. Let's play around inside distrobox. My Downloads are available as is! ``` sasank@my-distro:~$ ls ~/Downloads/ Miniconda3-py311_24.7.1-0-Linux-x86_64.sh ``` You can do sudo inside as well! ``` sasank@my-distro:~$ sudo apt install htop Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libnl-3-200 libnl-genl-3-200 Suggested packages: lm-sensors strace The following NEW packages will be installed: htop libnl-3-200 libnl-genl-3-200 0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded. Need to get 200 kB of archives. After this operation, 589 kB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 libnl-3-200 amd64 3.5.0-0.1 [59.1 kB] Get:2 http://archive.ubuntu.com/ubuntu jammy/main amd64 libnl-genl-3-200 amd64 3.5.0-0.1 [12.4 kB] Get:3 http://archive.ubuntu.com/ubuntu jammy/main amd64 htop amd64 3.0.5-7build2 [128 kB] Fetched 200 kB in 1s (147 kB/s) Selecting previously unselected package libnl-3-200:amd64. (Reading database ... 27094 files and directories currently installed.) Preparing to unpack .../libnl-3-200_3.5.0-0.1_amd64.deb ... Unpacking libnl-3-200:amd64 (3.5.0-0.1) ... Selecting previously unselected package libnl-genl-3-200:amd64. Preparing to unpack .../libnl-genl-3-200_3.5.0-0.1_amd64.deb ... Unpacking libnl-genl-3-200:amd64 (3.5.0-0.1) ... Selecting previously unselected package htop. Preparing to unpack .../htop_3.0.5-7build2_amd64.deb ... Unpacking htop (3.0.5-7build2) ... Setting up libnl-3-200:amd64 (3.5.0-0.1) ... Setting up libnl-genl-3-200:amd64 (3.5.0-0.1) ... Setting up htop (3.0.5-7build2) ... Processing triggers for man-db (2.10.2-1) ... Processing triggers for hicolor-icon-theme (0.17-2) ... Processing triggers for libc-bin (2.35-0ubuntu3.8) ... ``` To exit distrobox, just do exit or `ctrl+D`. ``` sasank@my-distro:~$ exit logout sasank@JOHNAIC:~$ ``` You can see you are back in the host machine. Try `sudo` now and you'll see it is refused. ``` sasank@JOHNAIC:~$ sudo apt install htop [sudo] password for sasank: sasank is not in the sudoers file. This incident will be reported. ``` You can access GPU inside distrobox as well! ``` sasank@my-distro:~$ nvidia-smi Sun Oct 13 19:32:06 2024 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 | |-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA GeForce RTX 4070 ... Off | 00000000:01:00.0 Off | N/A | | 0% 54C P0 34W / 285W | 14MiB / 16376MiB | 0% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | 0 N/A N/A 1174 G /usr/lib/xorg/Xorg 4MiB | +-----------------------------------------------------------------------------------------+ ``` Finally, let's verify that ports are exposed as well. ``` sasank@my-distro:~$ python3 -m http.server 8001 Serving HTTP on 0.0.0.0 port 8001 (http://0.0.0.0:8001/) ... ``` Now open another terminal in host and access the files ``` sasank@JOHNAIC:~$ curl 0.0.0.0:8001 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Directory listing for /</title> </head> <body> <h1>Directory listing for /</h1> <hr> <ul> <li><a href=".bash_eternal_history">.bash_eternal_history</a></li> <li><a href=".bash_history">.bash_history</a></li> <li><a href=".bash_logout">.bash_logout</a></li> <li><a href=".bashrc">.bashrc</a></li> <li><a href="Desktop/">Desktop/</a></li> <li><a href="Documents/">Documents/</a></li> <li><a href="Downloads/">Downloads/</a></li> <li><a href="Music/">Music/</a></li> <li><a href="Pictures/">Pictures/</a></li> <li><a href="Public/">Public/</a></li> <li><a href="Videos/">Videos/</a></li> </ul> <hr> </body> </html> ``` `ls` or `list` is another useful command in distrobox to see list of running distroboxes. ``` sasank@JOHNAIC:~$ distrobox ls ID | NAME | STATUS | IMAGE ae35c77dcc9d | test | Up 5 days | docker.io/library/ubuntu:22.04 5fb1b176afd5 | my-distro | Up 10 minutes | docker.io/library/ubuntu:22.04 ``` You can run multiple distroboxes in parallel! ### systemd *inside* distrobox There are some limitations to distrobox. Most containers are designed to be *application* containers. This means there's no `init` system like systemd inside the container. For example, `ubuntu:22.04` doesn't have `systemd` in it. This results in some subtle issues when installing a few packages. For example, let's try to install [caddy](https://caddyserver.com/docs/install#debian-ubuntu-raspbian) inside `my-distro` created above ``` sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy ``` You will notice errors like this ``` /usr/sbin/policy-rc.d returned 101, not running 'start caddy.service' ``` Similar issue can be found when install `redis` or `postgresql`: ``` invoke-rc.d: could not determine current runlevel invoke-rc.d: policy-rc.d denied execution of start. ``` This happens because these packages are looking to install a `systemd` service. We can get away from this errors by starting distrobox with init system as follows: ``` sasank@JOHNAIC:~$ distrobox create -n my-distro-init -i ubuntu:22.04 --init --additional-flags "--device nvidia.com/gpu=all" --additional-packages "systemd libpam-systemd pipewire-audio-client-libraries" ``` Let's break this command down * `-n` is short for `--name` * `-i` is short for `--image` * `--init` tells distrobox to setup init system and run as system container * `--additional-packages "systemd libpam-systemd pipewire-audio-client-libraries"` is required for `--init` to work because systemd is not installed in `ubuntu:22.04` image ``` sasank@JOHNAIC:~$ distrobox enter my-distro-init Starting container... [ OK ] Installing basic packages... [ OK ] Setting up devpts mounts... [ OK ] Setting up read-only mounts... [ OK ] Setting up read-write mounts... [ OK ] Setting up host's sockets integration... [ OK ] Integrating host's themes, icons, fonts... [ OK ] Setting up distrobox profile... [ OK ] Setting up sudo... [ OK ] Setting up groups... [ OK ] Setting up users... [ OK ] Setting up skel... [ OK ] Setting up init system... [ OK ] Firing up init system... [ OK ] Container Setup Complete! ``` Let's install `redis` and start the service ``` sasank@my-distro-init:~$ sudo apt install -y redis sasank@my-distro-init:~$ sudo service redis-server start sasank@my-distro-init:~$ sudo service redis-server status ● redis-server.service - Advanced key-value store Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2024-10-13 14:09:48 UTC; 917ms ago Docs: http://redis.io/documentation, man:redis-server(1) Main PID: 7946 (redis-server) Status: "Ready to accept connections" Tasks: 5 (limit: 307) Memory: 2.9M CPU: 537ms CGroup: /system.slice/redis-server.service └─7946 "/usr/bin/redis-server 127.0.0.1:6379" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ``` Thus, `systemd` is working inside distrobox! ## Conclusion In this post, we saw how containers create a sandbox environment and how this is not great experience for developers. Distrobox, running on rootless podman, is presented as a solution to this problem. In distrobox, we can use `sudo` even though the user doesn't have root permissions on the host system. We had a hands on session with distrobox where we installed the `htop` package inside distrobox using `sudo apt install htop`. We saw limitations of `ubuntu:22.04` based distrobox where systemd is not available inside. We found a fix for this by using `--init` and `--additional-packages` flags. > Published on 13/10/24