Intro to Linux Containers Internals: chroot Again

In our last post of the series #lci-series, we scratched the surface of what chroot does. It was great to get some feedback on how this lit some light bulbs. We can now agree on the definition that a container is a set of 1 or more processes isolated from the rest of the system. We saw this when we could isolate ls and bash just within our container-intro directory. This was just one aspect of isolation - filesystem isolation.

By the way, chroot was introduced in Unix around 1979. That’s how old this concept is, and that marked the early beginnings of containers as we know them today. You can read more about the history here - A Brief History of Containers: From the 1970s Till Now.

In this series, we would like to explore chroot further, this time with a full filesystem, as opposed to the hacky approach we used previously.

On your Linux system, start by installing debootstrap.

sudo apt install -y binutils debootstrap

Next, let’s create our directory for the root filesystem. If you are using WSL on Windows like me, make sure you are working from the linux filesystem and not the “Windows mount”, i.e. /mnt/c/.... Therefore, you can cd $HOME and work from there.

# assuming you are in $HOME
sudo mkdir ubuntufs

Let’s use debootstrap to create a Ubuntu 22.04 (jammy).

# jammy is the name for Ubuntu 22.04, see
sudo debootstrap --arch amd64 jammy ./ubuntufs
# after running, the last lins should read something like:
# ...
# I: Base system installed successfully.

ℹī¸ Note: another way of getting a filesystem without using debootstrap is exporting a Docker container and then extracting it with tar, e.g. docker export $(docker create ubuntu) | tar -C rootfs -xvf -. However, we said we don’t want to use Docker for demystification.

Now let’s chroot into the directory:

sudo chroot ubuntufs

And we are in:

root@nandaa-x1:/# ls
bin boot dev etc home lib lib32 ...

Let’s run ps to list processes, however, you will need to first mount /proc.

/proc is a virtual filesystem, sometimes referred to as a process information pseudo-file system; that contains runtime system information (e.g. system memory, devices mounted, hardware configuration, etc). You can read more about it here.

root@nandaa-x1:/# ps
Error, do this: mount -t proc proc /proc
root@nandaa-x1:/# mount -t proc proc /proc

root@nandaa-x1:/# ps aux
root         1  0.0  0.0   1744  1080 ?        Sl   15:27   0:00 /init
root         7  0.0  0.0   1752    68 ?        Ss   15:27   0:00 /init
root         8  0.0  0.0   1752    76 ?        S    15:27   0:00 /init
1000         9  0.0  0.0   6332  5368 ?        Ss   15:27   0:00 -bash
root     14351  0.0  0.0   8912  5564 ?        R+   15:55   0:00 sudo chroot co
root     14352  0.0  0.0   8912   880 ?        Ss   15:55   0:00 sudo chroot co
root     14353  0.0  0.0   5040  4000 ?        S    15:55   0:00 /bin/bash -i
root     14365  0.0  0.0   1752    68 ?        Ss   15:58   0:00 /init
root     14366  0.0  0.0   1752    76 ?        S    15:58   0:00 /init
1000     14367  0.0  0.0   6072  5180 ?        Ss+  15:58   0:00 -bash
root     14380  0.0  0.0   7784  3300 ?        R+   15:58   0:00 ps aux

In a new tab, if you ran ps aux on the main system, you should see a similar output. This is because we have not yet achieved namespace isolation, only filesystem isolation. We will cover namespace isolation in our next episode.

Thanks for reading this far; here is another small gift for you 🙂