Welcome to my tutorials on how to leverage Docker to deploy your application! This is meant as much to help me learn, so it doubles as a “what worked for me” series.
In the previous lesson, we had a brief primer on Docker and building images, created a two-line Dockerfile, and built and ran an image from that.
This lesson, we are going to add our own files into the image we want to create. After all, I don’t know anyone that wants to write their entire application as a sh script in the CMD line of a Dockerfile. So we will build two images. The first image will move the sh command into a file and run that. The second will expand on that and run a couple of different shell scripts.
Before starting, we need to review how files get stored in an image and discuss layers. An image is a virtual file system, composed of layers. An image can have multiple layers, and an image that is based on another image uses all the layers of that base image. Docker has two features that make this really neat: image layers overlay into the virtual filesystem, and each layer only contains changes. The first point means that the virtual filesystem is built by taking the files in the first layer, then adding the files from the second layer, and so on. This is like extracting a zip file, then extracting a second zip on top, then extracting a third on that. The files from the last layer wins in case of conflict, so if two layers had /bin/ls, the ls file from the last image is used. The second point means that the layers (and by extension the image) is small- only changes are tracked. In our last lesson, there were no new files-so that layer was really tiny. For this lesson, the layer only needs to store our one file and the code to run the script- the busybox image has all the layers we need for this to work.
For the first image, start by creating an empty folder for your image “project”. In there, create a file called helloworld.sh and add the following:
#!/bin/sh echo Hello World!
NOTE: If you are on Windows, the .sh files must be saved with Unix-style newlines. I was able to use Notepad++ and clicked on Edit->EOL Conversion->Unix to get this to work. Otherwise you get the error ‘standard_init_linux.go:211: exec user process caused “no such file or directory”‘ when you run the image in a container.
Also create a Dockerfile file in the folder:
FROM busybox COPY ./helloworld.sh /helloworld.sh CMD ["/helloworld.sh"]
This introduces the COPY command. COPY works very similar to the cp (or copy) shell command. The COPY command expects one or more source paths and one destination path. The source path is relative to the path given to docker build. The destination must be absolute, and uses the paths of the image’s virtual file system. Note that the files you want to copy must be present in the current or a child directory from where you run docker build
, as this directory gets temporarily copied into Docker for image creation.
Now change your direcotry to where your Dockerfile is at and let’s build and run this image:
docker build -t copyfile1 . docker run -it --rm copyfile1
Did you get that “SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host” message? This warning is because Windows does not use the Linux permission system, and Docker is warning you that it is enabling any user account in the Docker container to have read and execute permissions on all files and directories that you copied over. This will ensure that your image will run, but from a security perspective makes it possible for malicious software and their users to more easily access your data. We will cover how to correct this in a later lesson.
Now that we have run the file that we added to the image, let’s make things more complicated. Create a new folder and add the following three files.
#!/bin/sh echo What is your name? read name /greet.sh $name
!/bin/sh clear echo Hello, $1! echo At date I said hello to $1. ls /
Dockerfile
FROM busybox:glibc COPY ./*.sh / CMD ["/whoareyou.sh"]
Run the following in the new Dockerfile directory to bake your image and run it:
docker build -t copyfile2 . docker run -it --rm copyfile2
Now you should be prompted for your name, and when you hit enter, the screen clears, your image greets you, tells you when it greeted you, and lists the root of the image’s virtual filesystem. Not bad for a few lines of text in three files, right?
To summarize, we reviewed how to add files into an image and execute them. We also covered how the image is made of layers and why this saves space. Last, we explained why a shell script written on Windows might fail in an image if the newlines are not in Unix format.
For the next lesson, we will explore how to install software packages into our image.