Getting Started with Docker by Practice | 7. Bind Mount Hello Java
This section explains Docker’s bind mount feature. It also introduces how to compile and run a Java Hello World program as a concrete example of a bind mount.
Docker mount features
Many people already know the term “mount” as an operating system term in Linux, Windows, and other OSes. For example, “mounting a hard disk” means making the OS recognize the hard disk and put it into a usable state.
In Docker, a “mount” means making data outside a container available from inside the container. As of Docker v17.12.0-ce, released at the end of 2017, the following three mount features are provided.
- Bind mount (bind)
- Volume mount (volume)
- Temporary file system mount (tmpfs)
The “1. bind mount” explained here is a feature that makes Docker host files or directories available to a container. With a bind mount, files on the Docker host outside the container can be read and written from inside the container. A bind-mounted file can be used as if it were a file inside the container by using a path such as /home or /var.
“2. volume mount” is a feature that makes Docker-managed files or directories on the Docker host available. “3. temporary file system mount” is a feature for temporarily using data that you do not want to save as files on the Docker host. They are not explained in detail here, but you should use each mount type according to the purpose of the data.
Mount overview
Container isolation
Bind mounts have been provided since the early days of Docker and are still widely used. The reason bind mounts are needed is strongly related to one of the characteristics of containers: isolation.
In general, a container can only use the data inside that container. Data in another container or on the Docker host cannot be used from a container. For example, if you run a command such as ls /home in a container, the output shows only the /home information inside that container. Information from another container’s /home or the Docker host’s /home is not displayed at all. This characteristic is called container isolation.
Containers isolate not only files and directories, but also processes, networks, and other resources. Docker is called a type of virtualization technology because it isolates many things in this way and creates a virtually independent environment.
Isolation is a very important characteristic for Docker. High isolation has many advantages, such as reducing dependency on the surrounding environment, including the Docker host. On the other hand, high isolation can also be inconvenient.
For example, consider creating a small file and using it in a container, as with the Java Hello World program in this section. If you create the file on the Docker host, you cannot read or write it from inside the container. If you create the file in the container, you can of course read and write it, but the familiar text editor you normally use may not exist inside the container. Also, files created in the container are deleted together with the container when the container is removed. You can run the docker container cp command before deletion to copy the file out of the container, but it feels cumbersome.
Bind mounts make such situations possible by lowering isolation. They specially allow Docker host files, which normally cannot be read or written from inside a container, to be read and written if they are bind-mounted.
Preparation
Now let’s look at concrete usage of bind mounts using a Java Hello World program as an example. The flow is as follows.
- Preparation
- Isolation experiment: what happens without a bind mount
- Bind mount basics: the mount option
- Java compilation: the mount option
- Java execution: the workdir option and mount option
- Using aliases
- Bind mounting files on Windows
- The volume option
First, prepare a Docker host. The content here is verified on a Docker host created with VirtualBox on Windows 10 by using Docker Machine v0.12.2. For how to use Docker Machine, refer to “Creating a Docker Host on Windows with Docker Machine”.
Use Docker version v17.06 or later. This is because the --mount option is used later. The Docker version in this article is v17.12.0-ce.
Once the Docker host is ready, run a Java container. This time, use Docker’s official openjdk image.
Run the following command to check the Java version.
$ docker run --rm openjdk:9 java --version
openjdk 9.0.1
OpenJDK Runtime Environment (build 9.0.1+11-Debian-1)
OpenJDK 64-Bit Server VM (build 9.0.1+11-Debian-1, mixed mode)
The java --version command ran in a container created from the openjdk:9 image and printed information. Therefore, this section uses the image that includes JDK version 9 from Docker’s official openjdk image.
Note the --rm option in the command above. --rm automatically deletes the container when it stops. Since we will create and start Java containers several times later, containers would accumulate one after another if we did nothing, so we use --rm to remove them automatically.
Finally, create the following Java file on the Docker host.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
Save this as /home/docker/Hello.java. To repeat, this Hello.java is created on the Docker host.
Preparation is complete.
Isolation experiment: what happens without a bind mount
First, let’s confirm what happens without using a bind mount and what isolation means.
Run the following command.
$ ls /home/docker/Hello.java
/home/docker/Hello.java
$ docker run --rm openjdk:9 ls /home/docker/Hello.java
ls: cannot access '/home/docker/Hello.java': No such file or directory
The first ls runs on the Docker host, so it sees Hello.java in the Docker host’s /home/docker directory.
The second ls runs in the container, so it looks for the /home/docker directory inside the container. Because the directories in the container are isolated, it cannot find the Docker host’s Hello.java. (A container created from the openjdk image originally has a /home directory, but it does not have a /home/docker directory.)
Let’s try another experiment. This time, run the cat command and print the contents of Hello.java.
$ cat /home/docker/Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
$ docker run --rm openjdk:9 cat /home/docker/Hello.java
cat: /home/docker/Hello.java: No such file or directory
Again, the second cat runs in the container, so it cannot find the Docker host’s Hello.java.
Finally, test whether Java compilation can be done in the container.
$ docker run --rm openjdk:9 javac Hello.java
javac: file not found: Hello.java
Usage: javac <options> <source files>
use --help for a list of possible options
An error is printed again. The javac command ran in the container and tried to compile Hello.java, but it could not find the Docker host’s Hello.java.
Although an official JDK container can be created easily, Hello.java cannot be compiled as-is.
[Container isolation]
Bind mount basics: the mount option
Now use a bind mount so that Hello.java can be used from the container.
Bind mount settings are specified with the --mount option. Configure a bind mount as follows and run the cat command.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 cat /home/test/Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
The contents of Hello.java are printed. The cat command run in the container displays the Docker host’s Hello.java.
Let’s look at the --mount option. The --mount option describes mount settings in key=value format. Multiple key=value entries are separated by commas. Be careful not to insert spaces around the commas. The meaning of each key=value is as follows.
type=bind- This is the mount type. The available values are the three introduced earlier:
bind,volume, andtmpfs. Here,bindis specified to perform a bind mount.
- This is the mount type. The available values are the three introduced earlier:
src=/home/docker- This is the Docker host directory to mount. Here,
/home/docker, which containsHello.java, is specified. You may also specify the parent/home. In that case,/home/dockerandHello.javaunder/homeare also mounted. You can writesourceinstead ofsrc; it has the same meaning.
- This is the Docker host directory to mount. Here,
dst=/home/test- This is the path inside the container where the Docker host directory is mounted. In the container,
/home/dockerspecified bysrccan be used at the/home/testpath. The/home/testdirectory does not need to already exist inside the original container. You can writedestinationortargetinstead ofdst; they have the same meaning.
- This is the path inside the container where the Docker host directory is mounted. In the container,
Bind mount
Java compilation: the mount option
Now that bind mounting is possible, compile Hello.java.
Run the following command.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 javac /home/test/Hello.java
Now compilation is complete. Check it.
$ ls /home/docker/Hello*
/home/docker/Hello.class /home/docker/Hello.java
Hello.class has been created. Compilation succeeded.
This shows that by using a bind mount, you can compile a Java file on the Docker host from the container and output the class file back to the Docker host.
Java execution: the workdir option and mount option
Next, run Hello.class.
The command to run Hello.class is java Hello. First, replace the javac ... command used for compilation with java Hello and run it.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 java Hello
Error: Could not find or load main class Hello
Caused by: java.lang.ClassNotFoundException: Hello
A ClassNotFoundException occurred. It seems that Hello.class cannot be found.
There are several ways to solve the problem of a missing class file, but here we will use the --workdir option, abbreviated as -w, to change the directory where the java command runs.
The --workdir option specifies which directory in the container should be used for work. The directory used for work is called the working directory below.
If you do not specify the --workdir option, the working directory is determined by the image settings. What was the working directory of the command that just caused ClassNotFoundException? Check it with the pwd command.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 pwd
/
The working directory was the root directory (/). The location of Hello.class is not the root directory but the /home/test directory specified by dst, so ClassNotFoundException occurred. If the working directory for running java Hello is changed from the root directory to /home/test, it should be able to find Hello.class.
So specify /home/test with the --workdir option as follows.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test --workdir /home/test openjdk:9 java Hello
Hello, World
It ran successfully. Hello, World succeeded.
The --workdir option is useful not only with bind mounts and Java containers, but also in many other situations. It is worth remembering for cases like this where a file that should exist cannot be found.
Using aliases
Bind mounts are useful, but the command is rather long. Shorten it with the Linux alias command as follows.
$ alias java='docker run --rm --mount type=bind,src=/home/docker,dst=/home/test --workdir /home/test openjdk:9 java'
$ java Hello
Hello, World
The java in the second command, java Hello, is an alias, so a container actually runs. By using alias like this, you can use a container as if it were a locally installed tool.
Bind mounting files on Windows
When creating a Docker host in VirtualBox on Windows by using Docker Machine, as in Creating a Docker Host on Windows with Docker Machine, you can bind mount files from Windows.
You can intuitively understand what this means by looking at the Docker host’s /c/Users folder as follows.
$ ls /c/Users
All Users Default User Public/
Default/ kimkc/ desktop.ini
The exact output depends on your Windows system, but it should look roughly similar to the above. Do the folders look familiar? They are Windows user folders.
This c/Users folder is a VirtualBox shared folder automatically configured by Docker Machine. Thanks to the shared folder, Windows user folders can be used as Docker host folders. If you bind mount this shared folder, for example with --mount src=/c/Users/kimkc, Windows files can be used as if they were bind-mounted.
The volume option
Finally, let’s look at the --volume option, abbreviated as -v.
You can perform roughly the same processing as the bind mount introduced here by using the --volume option. It existed long before --mount and has been used for a long time. This article does not explain it, but many sample code snippets and explanatory articles for the --volume option exist, so search for them if needed.
Docker recommends that people learning mount processing from now on use the easier-to-handle --mount option. The author thinks that learning the --mount option first will make it easier to understand Docker Compose, the clustering feature Swarm, and the --volume option introduced in the Deploying Multiple Containers with Docker Compose page. Therefore, it is recommended to start by learning the --mount option.
Conclusion
This section explained Docker’s bind mount feature. It also introduced how to compile and run a Java Hello World program as a concrete example of bind mounts.
A bind mount can be described as a feature that deliberately lowers container isolation, one of the characteristics of containers, so that containers can be used more conveniently. Use bind mounts well while being careful not to significantly undermine the advantages of containers.