What Really Happens When You Command ‘Docker Run’

Ravi Chandola
DevOps.dev
Published in
4 min readNov 10, 2023

--

Docker is a tool that makes it easier to create, deploy, and run applications by using containers. Containers allow a engineers to package up an application with all of the parts it needs, like libraries and other dependencies, and ship it all out as one package. This means the application will run on any other machine regardless of any customized settings that machine might have that could differ from the machine used for writing and testing the code.

this series of steps you’ve listed is actually a simplified explanation of how a Docker container is started and run. Let’s break it down:

  1. Mount a Directory: In Docker, this refers to setting up the filesystem for the container. The directory that is mounted is the root filesystem of the container, which is usually a Docker image that you’ve specified in the docker run command.
  2. Unshare a Namespace: Namespaces are a feature of the Linux kernel that Docker utilizes to provide isolation between containers. When Docker “unshares” a namespace, it’s creating a new namespace that is isolated from other namespaces, effectively providing a private workspace for the processes in the container.
  3. Create a Chroot Pointed to the Mounted Directory: Chroot is a Unix system call that changes the root directory for the current running process and its children. By creating a chroot pointed to the mounted directory, Docker is limiting the portion of the file system that the container can access to just the mounted directory (i.e., the container’s filesystem).
  4. Create Control Groups and Bind to PID within Namespace: Control Groups (cgroups) are another feature of the Linux kernel that Docker uses to limit and isolate the resource usage (CPU, memory, disk I/O, etc.) of containers. Docker creates a cgroup for the container and binds it to the PID (process ID) within the namespace, thereby ensuring that the resource limits apply to the container’s processes.
  5. Set Linux Capabilities to Chroot: Linux capabilities are a partitioning of the privileges traditionally associated with superuser into a larger set of distinct privileges. Docker sets specific capabilities for the chroot environment, limiting what processes in the container can do, as a way of enhancing security.
  6. Start App within the Namespace: Finally, Docker starts the application you’ve specified within the isolated and resource-limited environment (i.e., the container) it has set up. The application thinks it’s running on its own machine, but it’s actually running inside a Docker container on a host machine.

How does Docker, by executing a chroot operation to the mounted directory, restrict the container’s access to only that specific part of the filesystem (i.e., the container’s filesystem)?

The chroot is a command in UNIX and UNIX-like operating systems that changes the root directory. It effectively isolates the current process and its children from the rest of the system.

Consider this scenario: You have a directory structure like this on your host machine:

/
├── home
│ ├── user
│ │ ├── dir1
│ │ └── dir2
├── var
│ ├── log
│ └── lib
└── tmp

Now, you start a Docker container and mount home/user directory from your host machine to your Docker container. In the Docker container, the directory structure would look something like this:

/
├── dir1
└── dir2

Here, Docker has performed a chroot operation to the home/user directory. This means the root ("/") inside the Docker container corresponds to home/user on your host machine. The process inside the Docker container perceives this as its entire filesystem. It has no knowledge of, or access to, directories and files outside home/user directory from the host machine (like /var, /tmp etc.).

This operation is crucial for the isolation provided by Docker containers. Each container perceives itself as a separate system with its own filesystem, unaware of the fact it shares the host system’s resources with other containers. This approach allows containers to run in isolation without risking the security and integrity of the host machine or other containers.

Lets take another example to understand the same

docker run -p 8080:8080 -p 50000:50000 -v /your/home:/var/jenkins_home -d jenkins/jenkins

Let’s break down the command:

docker run: This is the command to create and start a new Docker container.

-p 8080:8080 -p 50000:50000: These options are telling Docker to map port 8080 and port 50000 of your host machine to port 8080 and port 50000 inside the Docker container, respectively. This allows you to access the Jenkins server using these ports on your host machine.

-v /your/home:/var/jenkins_home: This is a volume mount. It's telling Docker to map the /your/home directory on your host machine to /var/jenkins_home inside the Docker container.

-d: This option runs the container in detached mode, which means it runs in the background and doesn't occupy your command line.

jenkins/jenkins: This is the Docker image that the container is based on.

Now, let’s discuss the file structures:

On your host machine, you have a directory /your/home, which can contain any number of files or directories. For example:

/your/home
├── file1
├── file2
└── dir1

When you run the docker run command with the -v /your/home:/var/jenkins_home option, Docker mounts the /your/home directory from your host to the /var/jenkins_home directory inside the Docker container.

So, inside the Docker container, the file structure would be like this:

/var/jenkins_home
├── file1
├── file2
└── dir1

This means that Jenkins, running inside the Docker container, will read and write data to /var/jenkins_home, which corresponds to /your/home on your host machine. Any changes made by Jenkins to /var/jenkins_home will be reflected in /your/home on your host machine, and vice versa.

This is a handy feature when you want the data to survive even after the container has been stopped or deleted because the data resides on the host machine and not inside the container.

--

--