Container Creation Using Namespaces and Bash
Tags: docker, containers, namespaces, linux
Published: 2018-10-18 15:19:11

---
title: Container Creation Using Namespaces and Bash
published: true
description: "This post follows along Jérôme Petazzoni demo from 'Cgroups, namespaces, and beyond: what are containers made from?' and explains in detail what each command does."
tags: ["docker", "containers", "namespaces", "linux"]
canonical_url: https://blog.nicolasmesa.co/posts/2018/08/container-creation-using-namespaces-and-bash/
---

Cross-posted from my personal blog [https://blog.nicolasmesa.co](https://blog.nicolasmesa.co).

A few weeks ago, I saw a great video explaining how Docker works under the hood (see video below). The video ends with a demo where Jérôme Petazzoni creates a container using nothing but bash. I found many of the commands that he used pretty cryptic, so I decided to explain what he did and the purpose of each command.

## Video

{% youtube sK5i-N34im8 %}

(The demo starts around minute 41)

## Terminology

Before we dive into the demo, let’s get some terminology out of the way.

### Container

A container is a combination of a few technologies including namespaces, cgroups, and capabilities. In this post, we’re going to focus on namespaces.

### Namespaces

From [Namespaces in operation](https://lwn.net/Articles/531114/):

> The purpose of each namespace is to wrap a particular global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource.

There are six types of namespaces in Linux:

1. Pid: Isolates process identifiers. For example, two processes in different namespaces can have the same _PID_.
2. User: Isolates user ids and group ids. Two users in two different user namespaces can have the same user ids. This is pretty useful because it allows mapping an unprivileged user id outside of the namespace to be root inside of the namespace.
3. Net: This namespace provides network isolation. Processes running in a separate net namespace don’t see the network interfaces of other namespaces.
4. Mnt: The mount namespace allows containers to have their own mount points without polluting the global namespace. It also provides a way to hide the global mount points from the container.
5. Uts: This allows a container to have its own hostname for the processes running in the container.
6. Ipc: Gives containers their own inter-process communication namespace.

**Note** : There is a lot more to namespaces than this. If you want to learn more, take a look at [Namespaces in operation](https://lwn.net/Articles/531114/).

### Btrfs

From the [Btrfs wiki](https://btrfs.wiki.kernel.org/index.php/Main_Page)

> Btrfs is a modern copy on write (CoW) filesystem for Linux aimed at implementing advanced features while also focusing on fault tolerance, repair, and easy administration.

## Setup

### System

For this demo, I used a computer running Ubuntu 16.04. To be more specific:

```console
nmesa@desktop-nicolas:~/demos/containers$ uname -a
Linux desktop-nicolas 4.13.0-39-generic #44~16.04.1-Ubuntu SMP Thu Apr 5 16:43:10 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
```

We also need _docker_ for two things:

1. To extract the _alpine_ docker image.
2. To use the bridge provided by _docker_ for networking.

This is the version of _docker_ that I had installed:

```console
root@desktop-nicolas:/btrfs# docker version
Client:
 Version: 18.06.0-ce
 API version: 1.38
 Go version: go1.10.3
 Git commit: 0ffa825
 Built: Wed Jul 18 19:11:02 2018
 OS/Arch: linux/amd64
 Experimental: false

Server:
 Engine:
  Version: 18.06.0-ce
  API version: 1.38 (minimum version 1.12)
  Go version: go1.10.3
  Git commit: 0ffa825
  Built: Wed Jul 18 19:09:05 2018
  OS/Arch: linux/amd64
  Experimental: false
```

### System setup

Before we start to follow along with the video, we to need setup our environment into the same state as Jérôme’s computer. We need to have a _btrfs_ filesystem mounted in _/btrfs_.

#### Create the disk image

We start by creating an empty disk image (from the [btrfs wiki](https://btrfs.wiki.kernel.org/index.php/Getting_started#Basic_Filesystem_Commands), the image should be at least 1GB in size).

```console
nmesa@desktop-nicolas:~/demos/containers$ dd if=/dev/zero of=disk.img bs=512 count=4194304
4194304+0 records in
4194304+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 4.60753 s, 466 MB/s

nmesa@desktop-nicolas:~/demos/containers$ ls -alh disk.img
-rw-rw-r-- 1 nmesa nmesa 2.0G Aug 23 22:06 disk.img
```

We use the `dd` command to create a 2GB empty image. The `dd` command receives four arguments:

- `if`: The file/device to use as the input. In this case, `/dev/zero`, which outputs a bunch of zeroes.
- `of`: The output file/device. In this case, `disk.img`.
- `bs`: This is the block size in bytes (512 bytes in this case).
- `count`: The number of `bs` blocks to read from `/dev/zero` and write to `disk.img`. In this case, 4194304 (2 \* 1024 \* 1024 \* 1024 / 512) to make the image 2GB.

#### Format disk image

Let’s format our new image with the _btrfs_ filesystem by using the `mkfs.btrfs` command.

```console
nmesa@desktop-nicolas:~/demos/containers$ mkfs.btrfs disk.img
btrfs-progs v4.4
See http://btrfs.wiki.kernel.org for more information.

Label: (null)
UUID: 772f0676-ec96-448d-b58c-31d4c10b5c3a
Node size: 16384
Sector size: 4096
Filesystem size: 2.00GiB
Block group profiles:
  Data: single 8.00MiB
  Metadata: DUP 110.38MiB
  System: DUP 12.00MiB
SSD detected: no
Incompat features: extref, skinny-metadata
Number of devices: 1
Devices:
   ID SIZE PATH
    1 2.00GiB disk.img
```

#### Mount the disk image

Great! We have formatted the filesystem! We need to mount the image to _/btrfs_ using the `mount` command (note that you have to be **root** ).


```console
root@desktop-nicolas:/home/nmesa/demos/containers# mkdir /btrfs
root@desktop-nicolas:/home/nmesa/demos/containers# mount -t btrfs disk.img /btrfs
```

We are ready to follow along with the video!

## Video breakdown

In this section, we follow along with the video, and I describe what each command does. Most commands require that you run them as **root**.

### Make mount private

```console
root@desktop-nicolas:/# mount --make-rprivate /
```

With this command, we make the mounts that our container is going to make private. This prevents any mounts that our container does, from being visible by the host system. The `r` in `--make-rprivate` stands for _recursive_ which means it makes all mount points private.

### Create container image

Let’s create two directories, one to hold the source images and one to hold the container images.

```console
root@desktop-nicolas:/btrfs# mkdir images containers
```

Then we use the `btrfs subvol create` command to create a new image called _alpine_.

 
```console
root@desktop-nicolas:/btrfs# btrfs subvol create images/alpine
Create subvolume 'images/alpine'
root@desktop-nicolas:/btrfs# ls images/alpine/
root@desktop-nicolas:/btrfs#
```

The `btrfs` command is used to control _btrfs_ filesystems. The `subvolume` subcommand is used to manage _btrfs subvolumes_. We use the `create` subcommand to create a subvolume named _alpine_ in the _images_ directory.

We have an image, but it’s empty. We use `docker` to get the _alpine_ image.
 
```console
root@desktop-nicolas:/btrfs# CID=$(docker run -d alpine true)
root@desktop-nicolas:/btrfs# echo $CID
9b290758c1734811c85445640c985fed9803121891d6604b4260e985c2647404
root@desktop-nicolas:/btrfs# docker export $CID | tar -C images/alpine/ -xf-
root@desktop-nicolas:/btrfs# ls images/alpine/
bin dev etc home lib media mnt proc root run sbin srv sys tmp usr var
```

We run `docker run -d alpine true` and assign its output (the container id) to the `CID` variable. Here’s a better explanation of the `docker` command:

- `run`: runs a container.
- `-d`: Detaches the container and prints the container id. Our terminal doesn’t wait for the container to finish its execution.
- `alpine`: The image that the container should use.
- `true`: The command to run in the container.

We use the `docker export` command to export the image. We pipe that image to the `tar` command which decompresses it and puts the files in _images/alpine_. Then we run `ls images/alpine` to see the contents of the image.

The next step is to take a _snapshot_ of this image and put it in our containers folder. The snapshot’s name is [tupperware](https://en.wikipedia.org/wiki/Tupperware).

```console
root@desktop-nicolas:/btrfs# btrfs subvol snapshot images/alpine/ containers/tupperware
Create a snapshot of 'images/alpine/' in 'containers/tupperware'
root@desktop-nicolas:/btrfs# ls containers/tupperware/
bin dev etc home lib media mnt proc root run sbin srv sys tmp usr var
```

We used the `snapshot` subcommand to create a snapshot of the subvolume _alpine_ with the name _tupperware_ in the _containers_ directory. Snapshots are very efficient. They don’t copy the data from the source subvolume making them space efficient and very fast to create. If a snapshot is modified, the original subvolume won’t be affected. We can see this in the following example:

 
```console
root@desktop-nicolas:/btrfs# touch containers/tupperware/NICK_WAS_HERE
root@desktop-nicolas:/btrfs# ls containers/tupperware/
bin dev etc home lib media mnt NICK_WAS_HERE proc root run sbin srv sys tmp usr var
root@desktop-nicolas:/btrfs# ls images/alpine/
bin dev etc home lib media mnt proc root run sbin srv sys tmp usr var
```

In this example, we add a file called _NICK\_WAS\_HERE_ to the snapshot. Then we prove that it is visible in the snapshot but not in the original _alpine subvolume_. More information about the `btrfs` command can be found in [this post](https://www.linux.com/learn/how-create-and-manage-btrfs-snapshots-and-rollbacks-linux-part-2).

### Testing using chroot

Let’s use the `chroot` command to run `sh` inside the _tupperware snapshot_.


```console
root@desktop-nicolas:/btrfs# chroot containers/tupperware/ sh
/ # ls /
NICK_WAS_HERE dev home media proc run srv tmp var
bin etc lib mnt root sbin sys usr
/ # apk
apk-tools 2.10.0, compiled for x86_64.

Installing and removing packages:
  add Add PACKAGEs to 'world' and install (or upgrade) them, while ensuring that all dependencies are met
  del Remove PACKAGEs from 'world' and uninstall them

System maintenance:
  fix Repair package or upgrade it without modifying main dependencies
  update Update repository indexes from all remote repositories
  upgrade Upgrade currently installed packages to match repositories
  cache Download missing PACKAGEs to cache and/or delete unneeded files from cache

Querying information about packages:
  info Give detailed information about PACKAGEs or repositories
  list List packages by PATTERN and other criteria
  dot Generate graphviz graphs
  policy Show repository policy for packages

Repository maintenance:
  index Create repository index file from FILEs
  fetch Download PACKAGEs from global repositories to a local directory
  verify Verify package integrity and signature
  manifest Show checksums of package contents

Use apk <command> --help for command-specific help.
Use apk --help --verbose for a full command listing.

This apk has coffee making abilities.
/ # exit
root@desktop-nicolas:/btrfs#
```

`chroot` is used to change the root directory of a program. In the example above, the _/btrfs/container/tupperware_ directory becomes the root of the filesystem. We execute `apk`, which is only available in _alpine_, and then we exit the container.

Note that we didn’t use namespaces in this “container.” Let’s change that!

### Using namespaces
 
```console
root@desktop-nicolas:/btrfs# unshare --mount --uts --ipc --net --pid --fork bash
root@desktop-nicolas:/btrfs#
```

We use the `unshare` command to run a program (_bash_) in different _mount_, _uts_, _ipc_, _net_ and _pid_ namespaces. We also pass in the `--fork` flag to create a new process as a child of the `unshare` command. It may seem like nothing happened from the command output, but we’re using namespaces and are somewhat isolated from the global namespace. Let’s change the _hostname_ to prove that we are in an isolated _uts_ namespace.

```console
root@desktop-nicolas:/btrfs# hostname tupperware
root@desktop-nicolas:/btrfs# exec bash
root@tupperware:/btrfs#
```

Note that after executing `bash`, our hostname switched to _tupperware_. Running `hostname` from another terminal shows that the global namespace still has the same _hostname_ (_desktop-nicolas_ in this case).


```console
# from a different terminal
nmesa@desktop-nicolas:~/demos/containers$ hostname
desktop-nicolas
```

Let’s do some experiments with the _pid namespace_.


```console
root@tupperware:/btrfs# ps
  PID TTY TIME CMD
24506 pts/26 00:00:00 bash
24551 pts/26 00:00:00 unshare
24552 pts/26 00:00:00 bash
24961 pts/26 00:00:00 ps
28780 pts/26 00:00:00 sudo
28781 pts/26 00:00:00 su
28782 pts/26 00:00:00 bash
root@tupperware:/btrfs# pidof unshare
24551
root@tupperware:/btrfs# kill $(pidof unshare)
bash: kill: (24551) - No such process
```

We see a lot more processes than expected. The _PIDs_ are also those of the main system. What is going on? It turns out that the _/proc_ of the global namespace is mounted, and the `ps` command uses _/proc_ to get some of its information. (You can verify this by running `strace ps`.)

### Mount proc

Let’s mount _/proc_.
 
```console
root@tupperware:/btrfs# mount -t proc proc /proc
root@tupperware:/btrfs# ps
  PID TTY TIME CMD
    1 pts/26 00:00:00 bash
   30 pts/26 00:00:00 ps
root@tupperware:/btrfs# umount /proc
root@tupperware:/btrfs#
```

After we mount _proc_, `ps` only shows the processes running in our container with the correct _PIDs_. We unmount _/proc_ again for now.

We’ve used `mount` before. Let’s go over what it does.

The `mount` command is used to mount filesystems and usually has the following syntax:

```console
mount -t <type> <device> <directory>
```

From the [mount man page](https://linux.die.net/man/8/mount):

> this tells the kernel to attach the filesystem found on `<device>` (which is of type `<type>`) at the directory `<directory>`.

Here’s the argument breakdown for our specific case:

- `-t proc`: The type of filesystem to use is _proc_.
- `proc`: The _/proc_ filesystem is not attached to a device. Any keyword here would work.
  - Note that in the video Jérôme uses _none_ instead of _proc_ for this value. I changed this to be _proc_ because the [mount man page](https://linux.die.net/man/8/mount) discourages the use of _none_ for this (“The customary choice _none_ is less fortunate: the error message ‘none busy’ from umount can be confusing.“).
- `/proc`: The directory where we want to mount the _proc_ filesystem.

### Pivot root

Let’s make _/btrfs/containers/tupperware_ the root of our filesystem by using `mount` and `pivot_root`.

```console
root@tupperware:/btrfs# mkdir /btrfs/containers/tupperware/oldroot
root@tupperware:/btrfs# mount --bind /btrfs/containers/tupperware /btrfs
root@tupperware:/btrfs# cd /btrfs/
root@tupperware:/btrfs# ls
bin dev etc home lib media mnt NICK_WAS_HERE oldroot proc root run sbin srv sys tmp usr var
root@tupperware:/btrfs# pivot_root . oldroot
root@tupperware:/btrfs# cd /
root@tupperware:/# ls
NICK_WAS_HERE dev home media oldroot root sbin sys usr
bin etc lib mnt proc run srv tmp var
root@tupperware:/# ls oldroot/
bin cdrom hello lib media root srv usr
boot core home lib32 mnt run sys var
bt dev initrd.img lib64 opt sbin tes vmlinuz
btrfs etc initrd.img.old lost+found proc snap tmp vmlinuz.old
```

Let’s break this down:

1. We create a directory called _oldroot_ where our current root filesystem will be mounted. As Jérôme explained in the video, to be able to run `pivot_root` successfully, you need to be close to the top of the filesystem (_/_).
2. We use `mount --bind` to get the container filesystem to _/btrfs_. (Jérôme splits this into two steps, and I didn’t understand why. I decided to try it this way to see if it would work and it did). The `mount --bind` command is useful to mount a directory somewhere else (in our case we’re mounting _/btrfs/containers/tupperware_ to _/btrfs_).
3. We go to _/btrfs_ and run `ls` to make sure that our container filesystem is mounted there.
4. We execute `pivot_root` which switches the current directory (_/btrfs_) to be the new root and mounts the current root in _oldroot_.
5. We move to the new root and check that it is mounted correctly by running `ls`.
6. We verify that _oldroot_ has the old root mount point.

**Note** : I didn’t understand why we needed to do a `pivot_root` instead of doing a bind mount point to `/`. [This email by Linus Torvalds himself](https://yarchive.net/comp/linux/pivot_root.html) helped me understand. In a nutshell, `/` points to the **process’ root of the filesystem**. By doing a bind mount to root (`/`), the process would still point to the old version of `/`.

### Fixing the mount points

Let’s take a look at our mount points.

```console
root@tupperware:/# mount -t proc proc /proc
root@tupperware:/# mount | head
/dev/sda2 on /oldroot type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda2 on /oldroot type ext4 (rw,relatime,errors=remount-ro,data=ordered)
udev on /oldroot/dev type devtmpfs (rw,nosuid,relatime,size=16404692k,nr_inodes=4101173,mode=755)
devpts on /oldroot/dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /oldroot/dev/shm type tmpfs (rw,nosuid,nodev)
mqueue on /oldroot/dev/mqueue type mqueue (rw,relatime)
hugetlbfs on /oldroot/dev/hugepages type hugetlbfs (rw,relatime,pagesize=2M)
tmpfs on /oldroot/run type tmpfs (rw,nosuid,noexec,relatime,size=3286976k,mode=755)
tmpfs on /oldroot/run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
cgmfs on /oldroot/run/cgmanager/fs type tmpfs (rw,relatime,size=100k,mode=755)
```

We mount _/proc_ first since `mount` relies on it. (You can verify this by running `strace mount`).

**Note** : The mount point list was pretty, big so I piped it to `head` to show the first 10 lines.

Most of these mounts shouldn’t be part of our container so let’s unmount everything and mount _/proc_ again.
 
```console
root@tupperware:/# umount -a
umount: can't unmount /oldroot/btrfs: Resource busy
umount: can't unmount /oldroot: Resource busy
umount: can't unmount /oldroot: Resource busy
root@tupperware:/# mount -t proc proc /proc
root@tupperware:/# mount
/dev/sda2 on /oldroot type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda2 on /oldroot type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/loop0 on /oldroot/btrfs type btrfs (ro,relatime,space_cache,subvolid=5,subvol=/)
/dev/loop0 on / type btrfs (ro,relatime,space_cache,subvolid=258,subvol=/containers/tupperware)
proc on /proc type proc (rw,relatime)
```

`umount` failed for _/oldroot_ because the resource was busy. This is why _/oldroot_ still shows up when we run `mount`. Let’s fix that.


```console
root@tupperware:/# umount -l /oldroot
root@tupperware:/# mount
/dev/loop0 on / type btrfs (ro,relatime,space_cache,subvolid=258,subvol=/containers/tupperware)
/dev/loop0 on / type btrfs (ro,relatime,space_cache,subvolid=258,subvol=/containers/tupperware)
proc on /proc type proc (rw,relatime)
```

The `-l` flag in the `umount` command stands for lazy. According to the [umount man page](https://linux.die.net/man/8/umount), this flag will “detach the filesystem from the file hierarchy now, and clean up all references to this filesystem as soon as it is not busy anymore.” As a result, we don’t see any references to _/oldroot_ when we rerun `mount`.

### Networking

Let’s verify that we don’t have access to the network.

 
```console
root@tupperware:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: sendto: Network unreachable
root@tupperware:/# ifconfig -a
lo Link encap:Local Loopback
          LOOPBACK MTU:65536 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```

Good! We don’t have network access, and we only have the loopback interface. Let’s fix that.

**Note** : We run the following commands from a separate terminal (not in our container).

We need the process id of our container (the _PID_ of `unshare`).

```console
root@desktop-nicolas:/home/nmesa# CPID=$(pidof unshare)
root@desktop-nicolas:/home/nmesa# echo $CPID
24551
```

We assign the _PID_ to the `CPID` variable to make it easier to copy/paste commands if you are following along. Let’s create a pair of virtual network interfaces.


```console
root@desktop-nicolas:/home/nmesa# ip link add name h$CPID type veth peer name c$CPID
```


The `ip link add` command creates two interfaces of type `veth` (virtual ethernet interface). One with name _h24551_ and the other one with name _c24551_. _h_ stands for host, _c_ stands for container and _24551_ is the _PID_ of our container.

Let’s move the _c24551_ interface to our container (note this command is also executed from the host).


```console
root@desktop-nicolas:/home/nmesa# ip link set c$CPID netns $CPID
```

The `ip link set` command sets configuration attributes to interfaces. In this case, it sets the _netns_ (network namespace) to be the same as the process id stored in _$CPID_ (_24551_ in our case).

Let’s get back to our container terminal to verify that it got the new interface.


```console
root@tupperware:/# ifconfig -a
c24551 Link encap:Ethernet HWaddr 96:18:F1:61:12:A2
          BROADCAST MULTICAST MTU:1500 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback
          LOOPBACK MTU:65536 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```

Great! We have the interface inside our container! Let’s get back to our host terminal and attach the _h24551_ interface to the docker bridge (_docker0_).


```console
root@desktop-nicolas:/home/nmesa# ip link set h$CPID master docker0 up
root@desktop-nicolas:/home/nmesa# ifconfig docker0
docker0 Link encap:Ethernet HWaddr 02:42:ac:03:c8:91
          inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe03:c891/64 Scope:Link
          UP BROADCAST MULTICAST MTU:1500 Metric:1
          RX packets:77485 errors:0 dropped:0 overruns:0 frame:0
          TX packets:77624 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:6199951 (6.1 MB) TX bytes:164681776 (164.6 MB)
```

We use the `ip link set` command to attach the interface to the _docker0_ bridge. We also grab the ip address of our _docker0_ interface: _172.17.0.1_ (write it down as you’ll need this later).

**Note** : Jérôme didn’t do this last step, and I suspect this is why the demo didn’t work as expected for him.

Let’s go back to our container terminal and configure the network interface.

```console
root@tupperware:/# ip link set lo up
root@tupperware:/# ip link set c24551 name eth0 up
root@tupperware:/# ip addr add 172.17.42.3/16 dev eth0
root@tupperware:/# ip route add default via 172.17.0.1
root@tupperware:/# ifconfig
eth0 Link encap:Ethernet HWaddr 96:18:F1:61:12:A2
          inet addr:172.17.42.3 Bcast:0.0.0.0 Mask:255.255.0.0
          inet6 addr: fe80::9418:f1ff:fe61:12a2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
          RX packets:35 errors:0 dropped:0 overruns:0 frame:0
          TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:3789 (3.7 KiB) TX bytes:936 (936.0 B)

lo Link encap:Local Loopback
          inet addr:127.0.0.1 Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING MTU:65536 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```

Let’s break this down:

1. We bring up the loopback interface.
2. We bring up the _c24551_ interface and rename it to _eth0_. Note that this command can’t be copied and pasted since your interface probably has a different name.
3. We assign the ip address of _172.17.42.3_ to our _eth0_ interface. Note that this ip address has to be within the range that we got back from the _docker0_ interface (172.17.0.0/16 in our case).
4. We set the default gateway of our container to be _172.17.0.1_ (this should be the ip address that we got from _docker0_).
5. We run a quick `ifconfig` to make sure everything is setup correctly.

Now, the moment of truth. Let’s ping _8.8.8.8_.

```console
root@tupperware:/# ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=121 time=11.045 ms
64 bytes from 8.8.8.8: seq=1 ttl=121 time=10.981 ms
64 bytes from 8.8.8.8: seq=2 ttl=121 time=10.508 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 10.508/10.844/11.045 ms
```

Our container has network connectivity!

### Let’s finish the job

As Jérôme noted in the video, we are still running _bash_ inside the container, but the _alpine_ image doesn’t have _bash_. Up until this point, we’ve been doing the container runtime’s job. To finish the container handoff, we run the following command:

```console
root@tupperware:/# exec chroot / sh
/ #
```

The `exec` command is a built-in utility in _bash_ (who knew?). It replaces the shell with the command (`chroot` in our case) without starting a new process. Once this command is executed, we’re officially in a container. Let’s run a few commands.

```console
/ # apk
apk-tools 2.10.0, compiled for x86_64.

Installing and removing packages:
  add Add PACKAGEs to 'world' and install (or upgrade) them, while ensuring that all dependencies are met
  del Remove PACKAGEs from 'world' and uninstall them

System maintenance:
  fix Repair package or upgrade it without modifying main dependencies
  update Update repository indexes from all remote repositories
  upgrade Upgrade currently installed packages to match repositories
  cache Download missing PACKAGEs to cache and/or delete unneeded files from cache

Querying information about packages:
  info Give detailed information about PACKAGEs or repositories
  list List packages by PATTERN and other criteria
  dot Generate graphviz graphs
  policy Show repository policy for packages

Repository maintenance:
  index Create repository index file from FILEs
  fetch Download PACKAGEs from global repositories to a local directory
  verify Verify package integrity and signature
  manifest Show checksums of package contents

Use apk <command> --help for command-specific help.
Use apk --help --verbose for a full command listing.

This apk has coffee making abilities.
/ # ls
NICK_WAS_HERE dev home media oldroot root sbin sys usr
bin etc lib mnt proc run srv tmp var
```

## Cleanup

Here are some cleanup steps commands.


```console
/ # exit
root@desktop-nicolas:/btrfs# exit
root@desktop-nicolas:/btrfs# cd /
root@desktop-nicolas:/# umount /btrfs
root@desktop-nicolas:/# exit
exit
nmesa@desktop-nicolas:~/demos/containers$ rm -f disk.img
nmesa@desktop-nicolas:~/demos/containers$
```

**Note** : I didn’t have to delete my _veth_ interfaces. I’m not sure if they are automatically removed once the network namespace dies.

## Conclusion

A lot goes on under the hood to provision containers! This was only covering the namespaces part. There’s also _cgroups_, _capabilities_, _devices_, etc. As Jérôme suggests, don’t create your own container runtime and use [docker](https://www.docker.com/) instead.

## Links / Further reading

- [Original Post](https://blog.nicolasmesa.co/posts/2018/08/container-creation-using-namespaces-and-bash/)
- [BTRFS](https://btrfs.wiki.kernel.org/index.php/Main_Page)
- [Getting started with BTRFS](https://btrfs.wiki.kernel.org/index.php/Getting_started#Basic_Filesystem_Commands)
- [BTRFS tutorial](https://www.linux.com/learn/how-manage-btrfs-storage-pools-subvolumes-and-snapshots-linux-part-1)
- [mount man page](https://linux.die.net/man/8/mount)
- [umount man page](https://linux.die.net/man/8/umount)
- [docker](https://www.docker.com/)
- [Building containers in pure bash and C](https://containersummit.io/events/nyc-2016/videos/building-containers-in-pure-bash-and-c)
- [Namespaces in operation](https://lwn.net/Articles/531114/)