By now, we are considerably familiar with the basic Docker commands. Let's up the ante. For the next couple of commands, I am going to use one of my side projects. Feel free to use a project of your own.
Let's start by listing out our requirements to determine the arguments we must pass to the docker run
command.
Our application is going to run on Node.js, so we will choose the well-maintained dockerfile/nodejs
image to start our base container:
8000
, so we will expose the port to 8000
of the host.$ docker run -it --name code.it dockerfile/nodejs /bin/bash [ root@3b0d5a04cdcd:/data ]$ cd /home [ root@3b0d5a04cdcd:/home ]$
Once you have started your container, you need to check whether the dependencies for your application are already available. In our case, we only need Git (apart from Node.js), which is already installed in the dockerfile/nodejs
image.
Now that our container is ready to run our application, all that is remaining is for us to fetch the source code and do the necessary setup to run the application:
$ git clone https://github.com/shrikrishnaholla/code.it.git $ cd code.it && git submodule update --init --recursive
This downloads the source code for a plugin used in the application.
Then run the following command:
$ npm install
Now all the node modules required to run the application are installed.
Next, run this command:
$ node app.js
Now you can go to localhost:8000
to use the application.
The
diff
command shows the difference between the container and the image it is based on. In this example, we are running a container with code.it
. In a separate tab, run this command:
$ docker diff code.it C /home A /home/code.it ...
The
commit
command creates a new image with the filesystem of the container. Just as with Git's commit
command, you can set a commit message that describes the image:
$ docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
For example, let's use this command to commit the container we have set up:
$ docker commit -m "Code.it – A browser based text editor and interpreter" -a "Shrikrishna Holla <s**[email protected]>" code.it shrikrishna/code.it:v1
The output will be a lengthy image ID. If you look at the command closely, we have named the image shrikrishna/code.it:v1
. This is a convention. The first part of an image/repository's name (before the forward slash) is the Docker Hub username of the author. The second part is the intended application or image name. The third part is a tag (usually a version description) separated from the second part by a colon.
Docker
Hub
is a public registry maintained by Docker, Inc. It hosts public Docker images and provides services to help you build and manage your Docker environment. More details about it can be found at https://hub.docker.com.
A collection of images tagged with different versions is a repository. The image you create by running the docker commit
command will be a local one, which means that you will be able to run containers from it but it won't be available publicly. To make it public or to push to your private Docker registry, use the docker push
command.
The
images
command lists all the images in the system:
$ docker images [OPTIONS] [NAME]
Flag |
Explanation |
---|---|
-a, --all
| |
-f, --filter=[]
| |
--no-trunc
| |
-q, --quiet
|
Now let's look at a few examples of the usage of the image
command:
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE shrikrishna/code.it v1 a7cb6737a2f6 6m ago 704.4 MB
This lists all top-level images, their repository and tags, and their virtual size.
Docker images are nothing but a stack of read-only filesystem layers. A union filesystem, such as AUFS, then merges these layers and they appear to be one filesystem.
In Docker-speak, a read-only layer is an image. It never changes. When running a container, the processes think that the entire filesystem is read-write. But the changes go only at the topmost writeable layer, which is created when a container is started. The read-only layers of the image remain unchanged. When you commit a container, it freezes the top layer (the underlying layers are already frozen) and turns it into an image. Now, when a container is started this image, all the layers of the image (including the previously writeable layer) are read-only. All the changes are now made to a new writeable layer on top of all the underlying layers. However, because of how union filesystems (such as AUFS) work, the processes believe that the filesystem is read-write.
A rough schematic of the layers involved in our code.it
example is as follows:
At this point, it might be wise to think just how much effort is to be made by the union filesystems to merge all of these layers and provide a consistent performance. After some point, things inevitably break. AUFS, for instance, has a 42-layer limit. When the number of layers goes beyond this, it just doesn't allow the creation of any more layers and the build fails. Read https://github.com/docker/docker/issues/1171 for more information on this issue.
The following command lists the most recently created images:
$ docker images | head
The -f
flag can be given arguments of the key=value
type. It is frequently used to get the list of dangling images:
$ docker images -f "dangling=true"
This will display untagged images, that is, images that have been committed or built without a tag.
The rmi
command removes images. Removing an image also removes all the underlying images that it depends on and were downloaded when it was pulled:
$ docker rmi [OPTION] {IMAGE(s)]
Flag |
Explanation |
---|---|
-f, --force
|
This forcibly removes the image (or images). |
--no-prune
|
This command does not delete untagged parents. |
This command removes one of the images from your machine:
$ docker rmi test
The
save
command saves an image or repository in a tarball and this streams to the stdout
file, preserving the parent layers and metadata about the image:
$ docker save -o codeit.tar code.it
The -o
flag allows us to specify a file instead of streaming to the stdout
file. It is used to create a backup that can then be used with the docker load
command.
The
load
command loads an image from a tarball, restoring the filesystem layers and the metadata associated with the image:
$ docker load -i codeit.tar
The -i
flag allows us to specify a file instead of trying to get a stream from the stdin
file.
The
export
command saves the filesystem of a container as a tarball and streams to the stdout
file. It flattens filesystem layers. In other words, it merges all the filesystem layers. All of the metadata of the image history is lost in this process:
$ sudo Docker export red_panda > latest.tar
Here, red_panda
is the name of one of my containers.
The
import
command creates an empty filesystem image and imports the contents of the tarball to it. You have the option of tagging it the image:
$ docker import URL|- [REPOSITORY[:TAG]]
URLs must start with http
.
$ docker import http://example.com/test.tar.gz # Sample url
If you would like to import from a local directory or archive, you can use the - parameter to take the data from the stdin
file:
$ cat sample.tgz | docker import – testimage:imported
You can add a tag
command to an image. It helps identify a specific version of an image.
For example, the python
image name represents python:latest
, the latest version of Python available, which can change from time to time. But whenever it is updated, the older versions are tagged with the respective Python versions. So the python:2.7
command will have Python 2.7 installed. Thus, the tag
command can be used to represent versions of the images, or for any other purposes that need identification of the different versions of the image:
$ docker tag IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]
The REGISTRYHOST
command is only needed if you are using a private registry of your own. The same image can have multiple tags:
$ docker tag shrikrishna/code.it:v1 shrikrishna/code.it:latest
Now, running the docker images
command again will show that the same image has been tagged with both the v1
and latest
commands:
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE shrikrishna/code.it v1 a7cb6737a2f6 8 days ago 704.4 MB shrikrishna/code.it latest a7cb6737a2f6 8 days ago 704.4 MB
The login
command is used to register or log in to a Docker registry server. If no server is specified, https://index.docker.io/v1/ is the default:
$ Docker login [OPTIONS] [SERVER]
Flag |
Explanation |
---|---|
-e, --email=""
|
|
-p, --password=""
|
Password |
-u, --username=""
|
Username |
If the flags haven't been provided, the server will prompt you to provide the details. After the first login, the details will be stored in the $HOME/.dockercfg
path.
The
push
command is used to push an image to the public image registry or a private Docker registry:
$ docker push NAME[:TAG]
The
history
command shows the history of the image:
$ docker history shykes/nodejs IMAGE CREATED CREATED BY SIZE 6592508b0790 15 months ago /bin/sh -c wget http://nodejs. 15.07 MB 0a2ff988ae20 15 months ago /bin/sh -c apt-get install ... 25.49 MB 43c5d81f45de 15 months ago /bin/sh -c apt-get update 96.48 MB b750fe79269d 16 months ago /bin/bash 77 B 27cf78414709 16 months ago 175.3 MB
Once started, the
events
command prints all the events that are handled by the docker
daemon, in real time:
$ docker events [OPTIONS]
Flag |
Explanation |
---|---|
--since=""
| |
--until=""
|
For example the events
command is used as follows:
$ docker events
Now, in a different tab, run this command:
$ docker start code.it
Then run the following command:
$ docker stop code.it
Now go back to the tab running Docker events and see the output. It will be along these lines:
[2014-07-21 21:31:50 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) start [2014-07-21 21:31:57 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) stop [2014-07-21 21:31:57 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) die
You can use flags such as --since
and --until
to get the event logs of specific timeframes.
The
wait
command blocks until a container stops, then prints its exit code:
$ docker wait CONTAINER(s)
The build command builds an image from the source files at a specified path:
$ Docker build [OPTIONS] PATH | URL | -
This command uses a Dockerfile and a context to build a Docker image.
A Dockerfile is like a Makefile. It contains instructions on the various configurations and commands that need to be run in order to create an image. We will look at writing Dockerfiles in the next section.
The files at the PATH
or URL
paths are called context of the build. The context is used to refer to the files or folders in the Dockerfile, for instance in the ADD
instruction (and that is the reason an instruction such as ADD ../file.txt
won't work. It's not in the context!).
When a GitHub URL or a URL with the git://
protocol is given, the repository is used as the context. The repository and its submodules are recursively cloned in your local machine, and then uploaded to the docker
daemon as the context. This allows you to have Dockerfiles in your private Git repositories, which you can access from your local user credentials or from the Virtual Private Network (VPN).
Remember that Docker engine has both the docker
daemon and the Docker client. The commands that you give as a user are through the Docker client, which then talks to the docker
daemon (either through a TCP or a Unix socket), which does the necessary work. The docker
daemon and Docker host can be in different hosts (which is the premise with which boot2Docker works), with the DOCKER_HOST
environment variable set to the location of the remote docker
daemon.
When you give a context to the docker build
command, all the files in the local directory get tared and are sent to the docker
daemon. The PATH
variable specifies where to find the files for the context of the build in the docker
daemon. So when you run docker build .
, all the files in the current folder get uploaded, not just the ones listed to be added in the Dockerfile.
Since this can be a bit of a problem (as some systems such as Git and some IDEs such as Eclipse create hidden folders to store metadata), Docker provides a mechanism to ignore certain files or folders by creating a file called .dockerignore
in the PATH
variable with the necessary exclusion patterns. For an example, look up https://github.com/docker/docker/blob/master/.dockerignore.
If a plain URL is given or if the Dockerfile is streamed through the stdin
file, then no context is set. In these cases, the ADD
instruction works only if it refers to a remote URL.
Now let's build the code.it
example image through a Dockerfile. The instructions on how to create this Dockerfile are provided in the Dockerfile section.
At this point, you would have created a directory and placed the Dockerfile inside it. Now, on your terminal, go to that directory and execute the docker
build
command:
$ docker build -t shrikrishna/code.it:docker Dockerfile . Sending build context to Docker daemon 2.56 kB Sending build context to Docker daemon Step 0 : FROM Dockerfile/nodejs ---> 1535da87b710 Step 1 : MAINTAINER Shrikrishna Holla <s**[email protected]> ---> Running in e4be61c08592 ---> 4c0eabc44a95 Removing intermediate container e4be61c08592 Step 2 : WORKDIR /home ---> Running in 067e8951cb22 ---> 81ead6b62246 Removing intermediate container 067e8951cb22 . . . . . . . . . . Step 7 : EXPOSE 8000 ---> Running in 201e07ec35d3 ---> 1db6830431cd Removing intermediate container 201e07ec35d3 Step 8 : WORKDIR /home ---> Running in cd128a6f090c ---> ba05b89b9cc1 Removing intermediate container cd128a6f090c Step 9 : CMD ["/usr/bin/node", "/home/code.it/app.js"] ---> Running in 6da5d364e3e1 ---> 031e9ed9352c Removing intermediate container 6da5d364e3e1 Successfully built 031e9ed9352c
Now, you will be able to look at your newly built image in the output of Docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE shrikrishna/code.it Dockerfile 031e9ed9352c 21 hours ago 1.02 GB
To see the caching in action, run the same command again
$ docker build -t shrikrishna/code.it:dockerfile . Sending build context to Docker daemon 2.56 kB Sending build context to Docker daemon Step 0 : FROM dockerfile/nodejs ---> 1535da87b710 Step 1 : MAINTAINER Shrikrishna Holla <s**[email protected]> ---> Using cache ---> 4c0eabc44a95 Step 2 : WORKDIR /home ---> Using cache ---> 81ead6b62246 Step 3 : RUN git clone https://github.com/shrikrishnaholla/code.it.git ---> Using cache ---> adb4843236d4 Step 4 : WORKDIR code.it ---> Using cache ---> 755d248840bb Step 5 : RUN git submodule update --init --recursive ---> Using cache ---> 2204a519efd3 Step 6 : RUN npm install ---> Using cache ---> 501e028d7945 Step 7 : EXPOSE 8000 ---> Using cache ---> 1db6830431cd Step 8 : WORKDIR /home ---> Using cache ---> ba05b89b9cc1 Step 9 : CMD ["/usr/bin/node", "/home/code.it/app.js"] ---> Using cache ---> 031e9ed9352c Successfully built 031e9ed9352c
An example of building an image using a repository URL is as follows:
$ docker build -t shrikrishna/optimus:git_url git://github.com/shrikrishnaholla/optimus Sending build context to Docker daemon 1.305 MB Sending build context to Docker daemon Step 0 : FROM dockerfile/nodejs ---> 1535da87b710 Step 1 : MAINTAINER Shrikrishna Holla ---> Running in d2aae3dba68c ---> 0e8636eac25b Removing intermediate container d2aae3dba68c Step 2 : RUN git clone https://github.com/pesos/optimus.git /home/optimus ---> Running in 0b46e254e90a . . . . . . . . . . . . . . . Step 5 : CMD ["/usr/local/bin/npm", "start"] ---> Running in 0e01c71faa0b ---> 0f0dd3deae65 Removing intermediate container 0e01c71faa0b Successfully built 0f0dd3deae65