Docker essentials – Images and Containers

I’ll admit, I jumped into docker pretty quickly and found that I was getting a little ahead of myself.  After much googling and discussion, I’ve come to realize a couple things about docker that I think are well worth sharing.  I’m hoping to share them through a series of ‘Docker Essentials’ type posts.

NOTE: My first post was going to be about how to interact with docker on the CLI.  However, there are lots of other resources out there for that.  Namely, the really awesome docker documentation.  In addition, the CLI provides help/syntax on commands as well.  I’ll list a couple of resources that I used and try to explain along the way, but if you don’t recognize a command I’m using look it up! 

The docker user guide – http://docs.docker.com/userguide/
The docker CLI reference – https://docs.docker.com/reference/commandline/cli/
The docker guide book – https://github.com/kencochrane/docker-guidebook

Working with images and Containers
On first glance, I made some assumptions about this images and containers that proved to be wrong.  So let’s start with some basics.

Images are read-only.  That is, they can never be altered.  Containers are built using images as the base.  For instance, at the end of the last post, we pulled down what’s referred to a ‘base image’ from the docker repository.  Some images are considered to be base images and they are officially created and supported by the good folks at docker.  So what happens when you modify an image?  Let’s look at the current images and see what we have…

image

As you can see, by downloading the base image from docker, we got a couple of versions of it.  We can see that there are 3 different images here.  When we work with the image, if we don’t specify an image, the ‘latest’ image will always be used. So let’s make a container based on the latest centos image…

image

You can see that we ‘ran’ the base image centos and created a new container called ‘FirstDocker’.  So now that we’ve done that, lets make some changes…

image

So we’ve made some changes at this point to the image.  How did we do that since images are always read only? The changes we made were actually made in another layer that’s on top of the base image.  Let’s take a look at our containers…

image

Here we can see our container.  I think its easiest to think of the container as the read/write layer that sits on top of a number of read-only images.  Recall how we created the directory ‘FirstDirectory’ and the file ‘FirstFile’ above?  Docker can take a look at the container and tell us what’s different between it and the base image.  We do that with the diff command…

image

Here we can see that our two files were added (Noted with the letter A, C is for changed).  When the container is run, docker merges all of the images required as well as the changes from the read/write layer in the container itself to make a running system.

Now, let’s say you’d like to merge these changes you’ve made into the image.  This can easily be done with the ‘commit’ command.  What actually happens sort of surprised me.  Let’s give it a whirl and see…

image

So the commit went well, we now see my image called local.  Now let’s check and see what docker sees as far as differences are concerned between the image and the container…

image

Interesting, it see the same changes.  But didn’t we just commit the container to the image?  We sure did, however, docker doesn’t do anything with the existing container.  Let’s take a look at the image tree and see what docker thinks our current image chain is…

image

So we can see that our current image (local) is the 4th image in the chain, and all we did was one commit.  So let’s delete the container and start a new container based on our new image…

image

As you can see, the new container has the changes that we made.  You can see that we have the new directory and the new file that we created.  So remember, if we made any changes to this container, the data would be stored in the container until we committed to the container to the image.  Also recall that the new image is a new ‘layer’ on top of the base image.  So let’s do that quick so you can see what I’m talking about…

image

So we made some new files and ran the diff.  As you can see, docker knows what files changed in the container based on the last image.  As you can see, the image chain hasn’t changed yet.  Let’s commit the changes and see what we get…

image

So now we can see that the image chain has changed.  Note that when I committed this image I used a ‘tag’ by saying the image was ‘2nd’.  What would happen if I were to use the same image name as the last time I committed?  Let’s try and see…

image

Interesting.  So I committed the exact same image name, but docker still inserted a layer.  Note in the last tree we had ‘local:latest’ with a image ID of 8681ece92ba0.  When I made the image tagged ‘2nd’, the ‘latest’ tagged image stayed with that image ID and a new image and ID (687049b86464) was created for the new image.  When I attempted to create an image with the same name, the initial base image (8681ece92ba0) stayed put, but the ‘latest’ image moved to a new image tagged a9172b14bc01.  So as you can see, docker maintains separate images for each commit regardless of the naming used.  To summarize, each commit makes a new image.  I’m not aware of any means to compress these images back into a base or master image.  This could cause some issues as the image stack can get quite large.  Recall though, the image only contains the changes (diffs) from the image before it to the current state.  So it’s generally not that much space, its just a matter of having a large stack of images at some point.

Options for running containers
Up to this point we’ve been running our containers in interactive mode.  We’ve done this be passing the ‘-t –i’ commands to the run command with docker as well as asking it to run the bash shell.  If we didn’t pass any of those flags to the run command, docker would just run the command you ask it to do in the container and then quit the container.  For instance, let’s create a new container and have it run a command, and see what happens…

image

So the first thing we do is create a new container called ‘OneTime’.  We passed the command ‘find / –name etc’ to the image to be executed.  We can see the results of the command run immediately after the container loaded.  The next thing we do is start the container again with the start command.  Docker kicks back the container name telling us that it ran.  Next we run the same command and pass the ‘-i’ flag to tell it to run the container again in interactive mode in which we again see the output of the container run.  So it appears that a docker run defaults to interactive mode whereas a docker start does not.

Keep in mind that once you run a container with a given command (in my example ‘find / –name etc’) that’s what’s associated with the container.  AKA, next time you start it, that’s the exact task it will run.  Im not aware of anyway to change that command that’s used during the initial run.

So we ran the command a total of 3 times.  If we wanted to see the output for all of the runs we could do so with the ‘logs’ command…

image

Here we see the output (output of STDOUT and STDERR I believe) of all of the container runs.  This is more useful when you are running the container as a daemon.  For instance, let’s create a container called ‘TwoTime’ and create it as a daemon.  After the initial run, we’ll start the container 3 more times.  Note once again that once you pass config (the daemon flag in this case) to the container during it’s build, it will be used during each consecutive run.  So each time we run, we won’t see any output unless we pass the ‘-i’ flag to run…

image

When we launch a container as a daemon, we can attach to the container to see what’s going on.  But before we do that, let’s talk about what happens when you start a process as a daemon.  For instance, let’s start /bin/bash as a daemon and see what we get…

image

As you can see, the process completed and then the container exited.  A daemonized (I think I just made that word up) container will ALWAYS exit once the process has completed.  On the flip side, let’s start the container in interactive mode and then exit and see what happens…

image

So let’s run through what we did above.  We started a container called WebApp in interactive mode, we exited interactive mode (by typing CTRL-P, CTRL-Q), then we checked to make sure the container was still running.  I let it sit for about an hour and then checked again to make sure it was still running.  Then I used the attach command to log back into interactive mode and then exit again.

So at this point, I think its fair to put some more formal definition around containers and images…

A docker layer – A set of read-only files.  There are both read-only and read-write images.  The top layer of a container is always the read-write layer of a container.  There may be numerous other read-only layers stacked below the read-write layer.

A docker image – A image is a read-only layer.  Images never change once they are created.  Images can be linked.  A image that is stacked on another image can be referred to in a child parent analogy with the lower image being the parent.  The bottommost image is referred to as the base image and has no parent image.

A docker container – When you start a container you reference an image.  Docker pulls the required image and pulls the images parent image.  It continues to pull the parent images until it reaches the base image.  In addition, it creates a read-write layer for changes to be stored in when the container is running.  All of these layers as well as other container configuration information is stored in the container.

So in this post we talked about how to create containers and images.  We also talked about how images stack and how containers contents can be committed to the image stack.  In the next post, we’ll take the stacking concept a bit further and talk about another way to deploy containers by building docker

Tags:

Reply

Your email address will not be published. Required fields are marked *