In this post we are going to create a container we can interact with. We can then have a poke around inside the container and see what it is and how it works.
Open your terminal and run:
docker run --rm -ti ubuntu /bin/bash
This will download the ubuntu image from Docker Hub and start a new container form it. There are a couple of new arguments here that we haven’t seen yet.
-t tells docker to assign a pseudo tty and
stdin open to allow us to interact with the shell. We have also added the command
/bin/bash to the end, this overrides the default command set in the image.
Once the container runs you should get something similar to:
The hostname of your container will be different but you should see that you are running a bash session as root within your container.
Seeing a prompt like this feels very much like being inside a virtual machine. However as this is a container you are sharing the kernel with the host system and any other containers you run simultaneously.
If you type
ls it will list the root filesystem of the container, this is the filesystem which came inside the Docker image and your bash process is contained inside it.
# ls bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr
You can move around the filesystem, edit files and execute commands. However if you create or modify a file and then exit from the container those changes will not be persisted. This is because each instance of a container is an ephemeral copy of the base image. Using a technology called copy-on-write Docker doesn’t actually have to copy the base image for each container. It stores all changes separately to the base image and merges them together cleverly when the containers accesses the filesystem. When the container exits these changes are discarded.
It is possible to persist data between containers using volumes, these will be covered in a later post.
You can also find the container images themselves stored under
/var/lib/docker on the host system. They are just directories like any other but Docker bind-mounts these to create the layered copy-on-write filesystem and uses
chroot to contain the processes within the image.
If you list the processes running on the system you will only see two, the bash session and the
# ps aux USER PID %CPU %MEM VSZ RSS START TIME COMMAND root 1 0.0 0.3 18180 3340 14:14 0:00 /bin/bash root 20 0.0 0.2 15572 2080 14:45 0:00 ps aux
This is different to a virtual machine in may ways. For starters your bash session has
PID 1. On all linux systems
PID 1 is the init system, that may be
upstart or something else. An init system handles the starting of all the other processes required to run your operating system, this includes networking, cron, disk mounts, graphical user interfaces and everything else you use. However in a container we already have a functioning operating system, the host. The container can therefore do one thing and one thing only without worrying about running an operating system, so having a single process with
PID 1 is logical.
If you were to run the
ps aux command on the linux host you would see all the running processes, including those inside the container.
$ ps aux USER PID %CPU %MEM VSZ RSS START TIME COMMAND root 1 0.1 0.1 9764 1736 13:49 0:05 init ... root 1522 0.0 0.3 18180 3340 14:14 0:00 /bin/bash ... docker 2028 0.0 0.1 13016 1740 14:45 0:00 ps aux
Here we can see the true
PID 1 of the system, we can also see the same
/bin/bash process from our container but with
PID 1522 instead of
1. The reason our container thinks the process is
PID 1 is due to kernel namespaces. The process is in both the main namespace, with
PID 1522, and in the container namespace with
PID 1. This is the containment part of containers, when we run
ps within the container it is restricted to the container namespace and can only see the other processes inside the container.
Containers appear to be a whole different system, but we’ve demonstrated here that they are just normal linux processes running on a normal linux machine. The only difference is they are kept separate from the other things running on the system and have access to a subset of resources. The exciting thing is each container can have a different subset of resources. They can behave like an entirely separate system with different tools and dependancies even if they conflict with the packages on the host system or in other containers.