Building docker images

building docker images
How to create your dock images –

On the previous post we explained the basics about managing existing Docker images. On this post we will describe how to create your own Docker images.

How to create your own Docker images

There are two ways to create a Docker image:
• Via the docker commit command
• Via the docker build command with a Dockerfile

The docker commit method is deprecated, building with Dockerfile is far more flexible and powerful, therefore we will explain how to create images using the build command.

Building images with a Dockerfile

Docker recommendation for creating images is to build images using a definition file called a Dockerfile and the docker build command. The Dockerfile uses a basic DSL (Domain Specific Language) with instructions for building Docker images. The Dockerfile approach is more repeatable, transparent, and idempotent mechanism for creating images.
Once we have a Dockerfile we then use the docker build command to build a new image from the instructions in the Dockerfile.

Creating a Dockerfile

Let’s now create a directory and an initial Dockerfile . We’re going to build a Docker image that contains a simple web server.

$ mkdir static_web
$ cd static_web/
$ touch Dockerfile

We’ve created a directory called static_web to hold our Dockerfile . This directory is our build environment, which is what Docker calls a context or build context. Docker will upload the build context, as well as any files and directories contained in it, to our Docker daemon when the build is run. This provides the Docker daemon with direct access to any code, files or other data you might want to include in the image.

We’ve also created an empty Dockerfile file to get started. Now let’s look at an example of a Dockerfile to create a Docker image that will act as a Web server.

Dockerfile to create a Docker image

# Version: 0.0.1
FROM ubuntu:16.04
RUN apt-get update; apt-get install -y nginx
RUN echo 'Hello World' >/var/www/html/index.html

The Dockerfile contains a series of instructions paired with arguments. Each instruction, for example FROM , should be in upper-case and be followed by an argument: FROM ubuntu:16.04 . Instructions in the Dockerfile are processed from the top down, so you should order them accordingly.
Each instruction adds a new layer to the image and then commits the image.
Docker executing instructions roughly follow a workflow:

  • Docker runs a container from the image.
  • An instruction executes and makes a change to the container.
  • Docker runs the equivalent of docker commit to commit a new layer.
  • Docker then runs a new container from this new image.
  • The next instruction in the file is executed, and the process repeats until all instructions have been executed.

This means that if your Dockerfile stops for some reason (for example, if an
instruction fails to complete), you will be left with an image you can use. This is highly useful for debugging: you can run a container from this image interactively and then debug why your instruction failed using the last image created.

The first instruction in a Dockerfile must be FROM . The FROM instruction specifies an existing image that the following instructions will operate on; this image is called the base image.
In our sample Dockerfile we’ve specified the ubuntu:16.04 image as our base image. This specification will build an image on top of an Ubuntu 16.04 base operating system. As with running a container, you should always be specific about exactly from which base image you are building.

We’ve followed these instructions with two RUN instructions. The RUN instruction executes commands on the current image. The commands in our example: updating the installed APT repositories and installing the nginx package and then creating the /var/www/html/index.html file containing some example text. As we’ve discovered, each of these instructions will create a new layer and, if successful, will commit that layer and then execute the next instruction.

By default, the RUN instruction executes inside a shell using the command wrapper /bin/sh -c . If you are running the instruction on a platform without a shell or you wish to execute without a shell you can specify the instruction in exec format:

 RUN [ "apt-get", " install", "-y", "nginx" ]

We use this format to specify an array containing the command to be executed and then each parameter to pass to the command.
Next, we’ve specified the EXPOSE instruction, which tells Docker that the application in this container will use this specific port on the container. That doesn’t mean you can automatically access whatever service is running on that port (here, port 80 ) on the container. For security reasons, Docker doesn’t open the port automatically, but waits for you to do it when you run the container using the docker run command. We’ll see this shortly when we create a new container from this image.
You can specify multiple EXPOSE instructions to mark multiple ports to be exposed.

Building the image from our Dockerfile

All of the instructions will be executed and committed and a new image returned when we run the docker build command. Let’s try that now:

First, remember to login using your docker hub account, if you don’t have one, create on at docker hub.

docker login
docker build -t="npucheta/static_web" .

You will see a bunch of stuff happening, we have shortened the output and left the most interesting parts below:

Sending build context to Docker daemon  2.048kB
 Step 1/4 : FROM ubuntu:16.04
  ---> 9361ce633ff1
 Step 2/4 : RUN apt-get update; apt-get install -y nginx
  ---> Running in 56fabc985e58
 Get:1 xenial-security InRelease [109 kB]
 Processing triggers for sgml-base (1.26+nmu4ubuntu1) …
 Processing triggers for systemd (229-4ubuntu21.16) …
 Removing intermediate container 56fabc985e58
  ---> db91da893446
 Step 3/4 : RUN echo 'Hello World' >/var/www/html/index.html
  ---> Running in 981ec57fa290
 Removing intermediate container 981ec57fa290
  ---> 97adde1ef809
 Step 4/4 : EXPOSE 80
  ---> Running in 38078b43687a
 Removing intermediate container 38078b43687a
  ---> 6a9db4f47ca3
 Successfully built 6a9db4f47ca3
 Successfully tagged npucheta/static_web:latest

You can see that each instruction in the Dockerfile has been executed with the image ID, 6a9db4f47ca3 , being returned as the final output of the build process. Each step and its associated instruction are run individually, and Docker has committed the result of each operation before outputting that final image ID.

Docker is able to be really clever about building images.

As a result of each step being committed as an image, Docker is able to be really clever about building images. It will treat previous layers as a cache.
This can save you a lot of time when building images if a previous step has not changed. If, however, you did change something, then Docker would restart from the first changed instruction.
Sometimes, though, you want to make sure you don’t use the cache. For example, if you’d cached Step 3 above, apt-get update , then it wouldn’t refresh the APT package cache. You might want it to do this to get a new version of a package. To skip the cache, we can use the –no-cache flag with the docker build command.

Now that we had created our image, lets check if the image is there with the docker images command:

# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
npucheta/static_web   latest              6a9db4f47ca3        6 minutes ago       199MB
nginx                 latest              27a188018e18        5 days ago          109MB
ubuntu                16.04               9361ce633ff1        5 weeks ago         118MB
ubuntu                latest              94e814e2efa8        5 weeks ago         88.9MB
fedora                21                  ba6369d667d1        2 years ago         241MB

You can see at the top of our images list our new image.

If we want to see how our image was created, we can use the docker history command.

docker history npucheta/static_web
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
6a9db4f47ca3        7 minutes ago       /bin/sh -c #(nop)  EXPOSE 80                    0B                  
97adde1ef809        7 minutes ago       /bin/sh -c echo 'Hello World' >/var/www/html…   12B                 
db91da893446        7 minutes ago       /bin/sh -c apt-get update; apt-get install -…   81.5MB              
9361ce633ff1        5 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           5 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           5 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B                  
<missing>           5 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>           5 weeks ago         /bin/sh -c #(nop) ADD file:c02de920036d851cc…   118MB               

Launching a container from our new image

Let’s launch a new container using our new image and see if what we’ve built has worked.

# docker run -d -p 80:80 --name static_web npucheta/static_web nginx -g "daemon off;"

Here I’ve launched a new container called static_web using the docker run command and the name of the image we’ve just created. We’ve specified the -d option, which tells Docker to run detached in the background. This allows us to run long-running processes like the Nginx daemon. We’ve also specified a command for the container to run: nginx -g “daemon off;” . This will launch Nginx in the foreground to run our web server.
We’ve also specified a new flag, -p . The -p flag manages which network ports Docker publishes at runtime.

Lets check now if the container was created and the port 80 open:

# docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                NAMES
2d369e06a58d        npucheta/static_web   "nginx -g 'daemon of…"   3 seconds ago       Up 2 seconds>80/tcp   static_web

We can now find out the IP of the container and then connect to check our Hello World page is UP:

# docker exec static_web hostname -i
# curl
Hello World

As the port 80 is bound to the address on the host we could also accomplished the same running:

# curl localhost
Hello World

That is all for now, we have created our own image based on NGINX and run our container successfully. Stay tune and as usual if you have any questions, don’t hesitate to get in touch.