Home > Blog > Docker Run: How to create images from an application

Docker Run: How to create images from an application

Nick Chase - December 6, 2016 - |

Whether you’re just getting started containerizing applications or you’re diving deeper into Kubernetes development, knowing how to create Docker images for applications is an essential skill. In this guide, you’ll learn how to create Docker images so you can deploy your own applications and make them available to other people.

How Docker images work

Before learning to create Docker images, the first thing that we need to understand is how Docker images themselves work.

The key to a Docker image is that it’s a layered file system. In other words, if you start out with an image that’s just the operating system (say Windows) and then add an application (say Nginx), you’ll wind up with something like this:

As you can see, the difference between IMAGE1 and IMAGE2 is just the application itself, and then IMAGE4 has the changes made on layers 3 and 4. So in order to create an image, you are basically starting with a base image and defining the changes to it.

Now, I hear you asking, “But what if I want to start from scratch?” Well, let’s define “from scratch.” Chances are, you mean that you want to start with a clean operating system and go from there. Well, in most cases there’s a base image already available.

But maybe you want to spin up a bare-bones foundation for your new container…using Docker’s scratch image. In that case, skip forward to the “Using a Dockerfile” section. (For further information on creating a Docker base image, including instructions on using tar to package a parent image, check out the Docker docs.)

Two ways to create a Docker image

In this guide, we’re going to look at two methods for creating a new Docker image:

  • Create a Docker image from an existing container: In this case, you start with an existing image, customize it with the changes you want, then build a new image from it.
  • Use a Dockerfile: In this case, you use a file of instructions — the Dockerfile — to specify the base image and the changes you want to make to it.

Let’s start by creating a new image from an existing container.

Creating a new Docker image from an existing container

When it comes to Docker, getting started can be pretty straightforward. In this example, we’re going to start with an image that includes the nginx web application server and PHP. To that, we’re going to add support for reading RSS data using an open-source package called SimplePie (available for download at GitHub). We’ll then make a new image out of the altered container.

Create the original Docker container

The first thing we need to do is instantiate the original base image, or have docker create a container from an image.

  1. The very first step is to make sure that your system has Docker installed.  If you followed our earlier series on running Kubernetes on OpenStack, you’ve already got this handled.  If not, you can follow the instructions here to just deploy Docker.
  2. Next, you’ll need to get the base image. For this tutorial, that’s webdevops/php-nginx, which you can find in the Docker Hub repository — in order to “pull” it, you’ll need a Docker Hub ID.  If you don’t have one already, go to https://hub.docker.com and create a free account.
  3. Go to the command line where you have Docker installed and log in to the Docker Hub:
    # docker login
    Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
    Username: nickchase
    Password:
    Login Succeeded
  4. We’re going to start with the base image and have Docker start a container.  Instantiate webdevops/php-nginx:
    # docker run -dP webdevops/php-nginx

    The -dP flag makes sure that the container runs in the background, and that the ports on which it listens are made available.

  5. Make sure the container is running:
    # docker ps
    CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                                                    NAMES
    1311034ca7dc        webdevops/php-nginx   "/opt/docker/bin/entr"   35 seconds ago      Up 34 seconds       0.0.0.0:32822->80/tcp, 0.0.0.0:32821->443/tcp, 0.0.0.0:32820->9000/tcp   small_bassi

A couple of notes here. First off, because we didn’t specify a particular name for the container, Docker assigned one.  In this example, it’s small_bassi.  Second, notice that there are 3 ports that are open: 80, 443, and 9000, and that they’ve been mapped to other ports (in this case 32822, 32821, and 32820, respectively — on your machine these ports will be different).  This makes it possible for multiple containers to be “listening” on the same port on the same host machine.  So if we were to try and access a web page being hosted by this container, we’d do it by accessing:

http://localhost:32822

So far, though, there aren’t any pages to access. Let’s fix that.

Create a file on the container

In order for us to test this container, we need to create a sample PHP file.  We’ll do that by logging into the container and creating a file.

  1. Login to the container
    # docker exec -it small_bassi /bin/bash
    root@1311034ca7dc:/#

    Using exec with the -it switch creates an interactive session for you to execute commands directly within the container. In this case, we’re executing /bin/bash, so we can do whatever else we need.

  2. The document root for the nginx server in this container is at /app, so go ahead and create the /app/index.php file:
    vi /app/index.php
  3. Add a simple PHP routine to the file and save it:
    <?php
    for ($i; $i < 10; $i++){
         echo "Item number ".$i."\n";
    }
    ?>
  4. Now exit the container to go back to the main command line:
    root@1311034ca7dc:/# exit
  5. Now let’s test the page.  To do that, execute a simple curl command:
    # curl http://localhost:32822/index.php
    Item number
    Item number 1
    Item number 2
    Item number 3
    Item number 4
    Item number 5
    Item number 6
    Item number 7
    Item number 8
    Item number 9

Now it’s time to go ahead and add RSS.

Make changes to the container

Now that we know PHP is working we can go ahead and add RSS support using the SimplePie package.  To do that, we’ll simply download it to the container and install it.

  1. The first step is to log back into the container:
    # docker exec -it small_bassi /bin/bash
    root@1311034ca7dc:/#
  2. Next go ahead and use curl to download the package, saving it as a zip file:
    root@1311034ca7dc:/# curl https://codeload.github.com/simplepie/simplepie/zip/1.4.3 > simplepie1.4.3.zip
  3. Now you need to install it.  To do that, unzip the package, create the appropriate directories, and copy the necessary files into them as follows:
    root@1311034ca7dc:/# unzip simplepie1.4.3.zip
    root@1311034ca7dc:/# mkdir /app/php
    root@1311034ca7dc:/# mkdir /app/cache
    root@1311034ca7dc:/# mkdir /app/php/library
    root@1311034ca7dc:/# cp -r s*/library/* /app/php/library/.
    root@1311034ca7dc:/# cp s*/autoloader.php /app/php/.
    root@1311034ca7dc:/# chmod 777 /app/cache
  4. Now we just need a test page to make sure that it’s working. Create a new file in the /app directory:
    root@1311034ca7dc:/# vi /app/rss.php
  5. Now add the content for the sample file.  (This file is excerpted from the SimplePie website, but I’ve cut it down for brevity’s sake, since it’s not really the focus of what we’re doing. Please see the original version for comments, etc.)
    <?php
    require_once('php/autoloader.php');
    $feed = new SimplePie();
    $feed->set_feed_url("http://rss.cnn.com/rss/edition.rss");
    $feed->init();
    $feed->handle_content_type();
    ?>
    <html>
    <head><title>Sample SimplePie Page</title></head>
    <body> 
     <div class="header">
     <h1><a href="<?php echo $feed->get_permalink(); ?>"><?php echo $feed->get_title(); ?></a></h1>
     <p><?php echo $feed->get_description(); ?></p>
     </div>
     <?php foreach ($feed->get_items() as $item): ?>
     <div class="item">
     <h2><a href="<?php echo $item->get_permalink(); ?>"><?php echo $item->get_title(); ?></a></h2>
     <p><?php echo $item->get_description(); ?></p>
     <p><small>Posted on <?php echo $item->get_date('j F Y | g:i a'); ?></small></p>
     </div> 
     <?php endforeach; ?>
    </body>
    </html>
  6. Exit the container:
    root@1311034ca7dc:/# exit
  7. Let’s make sure it’s working. Remember, we need to access the container on the alternate port (check docker ps to see what ports you need to use):
    # curl http://localhost:32822/rss.php
    <html>
    <head><title>Sample SimplePie Page</title></head>
    <body>
            <div class="header">
                    <h1><a href="http://www.cnn.com/intl_index.html">CNN.com - RSS Channel - Intl Homepage - News</a></h1>
                    <p>CNN.com delivers up-to-the-minute news and information on the latest top stories, weather, entertainment, politics and more.</p>
            </div>
    ...

Now, we can turn it into a new image.

Create the new image

Now let’s look at how to create a Docker image from the container. We have a working container and we want to turn it into an image and push it to  Docker Hub so community users can access it.  The name you’ll use for your container typically will have three parts:

[username]/[imagename]:[tags]

For example, my Docker Hub username is nickchase, so I am going to name version 1 of my new RSS-ified container

nickchase/rss-php-nginx:v1
  1. If you started to think about version control systems when we first discussed the differences between layers, you were right on target.  The first step in creating a new image is to commit the changes that we’ve already made, adding a message about the changes and specifying the author, as in:
    docker commit -m "Message" -a "Author Name" [containername] [imagename]

    So in my case, that will be:

    # docker commit -m "Added RSS" -a "Nick Chase" small_bassi nickchase/rss-php-nginx:v1
    sha256:148f1dbceb292b38b40ae6cb7f12f096acf95d85bb3ead40e07d6b1621ad529e
  2. Next we want to go ahead and push the new image to the Docker Hub so we can use it:
    # docker push nickchase/rss-php-nginx:v1
    The push refers to a repository [docker.io/nickchase/rss-php-nginx]
    69671563c949: Pushed
    3e78222b8621: Pushed
    5b33e5939134: Pushed
    54798bfbf935: Pushed
    b8c21f8faea9: Pushed
    ...
    v1: digest: sha256:48da56a77fe4ecff4917121365d8e0ce615ebbdfe31f48a996255f5592894e2b size: 3667
  3. Now if you list the images that are available, you should see it in the list:
    # docker images
    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
    nickchase/rss-php-nginx   v1                  148f1dbceb29        11 minutes ago      677 MB
    nginx                     latest              abf312888d13        3 days ago          181.5 MB
    webdevops/php-nginx       latest              93037e4c8998        3 days ago          675.4 MB
    ubuntu                    latest              e4415b714b62        2 weeks ago         128.1 MB
    hello-world               latest              c54a2cc56cbb        5 months ago        1.848 kB
  4. Now let’s go ahead and test it.  We’ll start by stopping the original container so we can remove the local copy of the image:
    # docker stop small_bassi
    # docker rm small_bassi
  5. Now we can remove the image itself:
    # docker rmi nickchase/rss-php-nginx:v1
    Untagged: nickchase/rss-php-nginx:v1
    Untagged: nickchase/rss-php-nginx@sha256:0a33c7a25a6d2db4b82517b039e9e21a77e5e2262206fdcac8b96f5afa64d96c
    Deleted: sha256:208c4fc237bb6b2d3ef8fa16a78e105d80d00d75fe0792e1dcc77aa0835455e3
    Deleted: sha256:d7de4d9c00136e2852c65e228944a3dea3712a4e7bcb477eb7393cd309be179b
  6. If you run docker images again, you’ll see that it’s gone:
    # docker images
    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
    nginx                     latest              abf312888d13        3 days ago          181.5 MB
    webdevops/php-nginx       latest              93037e4c8998        3 days ago          675.4 MB
    ubuntu                    latest              e4415b714b62        2 weeks ago         128.1 MB
    hello-world               latest              c54a2cc56cbb        5 months ago        1.848 kB
  7. Now if you create a new container based on this image, you will see it get downloaded from the Docker Hub:
    # docker run -dP nickchase/rss-php-nginx:v1
  8. Finally, test the new container by getting the new port…
    # docker ps
    CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                                                                    NAMES
    13a423324d80        nickchase/rss-php-nginx:v1   "/opt/docker/bin/entr"   6 seconds ago       Up 5 seconds        0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp   goofy_brahmagupta
  9. … and accessing the rss.php file.
    curl http://localhost:32825/rss.php

You should see the same output as before.

Use a Dockerfile

Manually creating a new image from an existing container gives you a lot of control, but it does have one downside. If the base container gets updated, you’re not necessarily going to have the benefits of those changes.

For example, suppose I wanted a container that always takes the latest version of a Linux operating system such as the Ubuntu operating system and builds on that. The previous method doesn’t give us that advantage.

Instead, we can use a method called the Dockerfile, which enables us to specify a particular version of a base image, or specify that we want to always use the latest version.  

For example, let’s say we want to create a version of the rss-php-nginx container that starts with v1 but serves on port 88 (rather than the traditional 80).  To do that, we basically want to perform three steps:

  1. Start with the desired base container.
  2. Tell Nginx to listen on port 88 rather than 80.
  3. Let Docker know that the container listens on port 88.

We’ll do that by creating a local context, downloading a local copy of the configuration file, updating it, and creating a Dockerfile that includes instructions for building the new container.

Let’s get that set up.

  1. Create a working directory in which to build your new container.  What you call it is completely up to you. I called mine k8stutorial.
  2. From the command line, In the local context, start by instantiating the image so we have something to work from:
    # docker run -dP nickchase/rss-php-nginx:v1
  3. Now get a copy of the existing vhost.conf file. In this particular container, you can find it at /opt/docker/etc/nginx/vhost.conf.  
    # docker cp amazing_minksy:/opt/docker/etc/nginx/vhost.conf .

    Note that I have a new container named amazing_minsky to replace small_bassi. At this point you should have a copy of vhost.conf in your local directory — so in my case, it would be ~/k8stutorial/vhost.conf.

  4. You now have a local copy of the vhost.conf file.  Using a text editor, open the file and specify that nginx should be listening on port 88 rather than port 80:
    server {
        listen   88 default_server;
        listen 8000 default_server;
        server_name  _ *.vm docker;
    ...
  5. Next, we want to go ahead and create the Dockerfile.  You can do this in any text editor.  The file, which should be called Dockerfile, should start by specifying the base image:
    FROM nickchase/rss-php-nginx:v1
  6. Any container that is instantiated from this image is going to be listening on port 80, so we want to go ahead and overwrite that Nginx config file with the one we’ve edited:
    FROM nickchase/rss-php-nginx:v1
    COPY vhost.conf /opt/docker/etc/nginx/vhost.conf
  7. Finally, we need to tell Docker that the container listens on port 88:
    FROM nickchase/rss-php-nginx:v1
    COPY vhost.conf /opt/docker/etc/nginx/vhost.conf
    EXPOSE 88
  8. Now we need to build the actual image. To do that, we’ll use the docker build command:
    # docker build -t nickchase/rss-php-nginx:v2 .
    Sending build context to Docker daemon 2.048 kB
    Step 1 : FROM nickchase/rss-php-nginx:v1
     ---> 208c4fc237bb
    Step 2 : EXPOSE 88
     ---> Running in 23408def6214
     ---> 93a43c3df834
    Removing intermediate container 23408def6214
    Successfully built 93a43c3df834

    Notice that we’ve specified the image name, along with a new tag (you can also create a completely new image) and the directory in which to find the Dockerfile and any supporting files.

  9. Finally, push the new image to the hub:
    # docker push nickchase/rss-php-nginx:v2
  10. Test out your new image by instantiating it and pulling up the test page.  with a command such as docker run <image>. For example:
    # docker run -dP nickchase/rss-php-nginx:v2
    root@kubeclient:/home/ubuntu/tutorial# docker ps
    CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                                                                                           NAMES
    04f4b384e8e2        nickchase/rss-php-nginx:v2   "/opt/docker/bin/entr"   8 seconds ago       Up 7 seconds        0.0.0.0:32829->80/tcp, 0.0.0.0:32828->88/tcp, 0.0.0.0:32827->443/tcp, 0.0.0.0:32826->9000/tcp   goofy_brahmagupta
    13a423324d80        nickchase/rss-php-nginx:v1   "/opt/docker/bin/entr"   12 minutes ago      Up 12 minutes       0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp                          amazing_minsky

Notice that you now have a mapped port for port 88 you can call:

curl http://localhost:32828/rss.php

Creating a Docker base image 

Remember when we said we could use a Dockerfile to create our own base image “from scratch”? In this case, that means building from a special, just-the-essentials image provided by Docker called scratch. In order to do this, we simply begin our Dockerfile with the following specification:

# syntax=docker/dockerfile:1
FROM scratch

When Docker goes to build your image, the system will treat the next line after FROM scratch as the image’s first filesystem layer.

Other things you can do with Dockerfile

Docker defines a whole list of things you can do with a Dockerfile, such as:

As you can see, there’s quite a bit of flexibility here.  You can see the documentation for more information, and wsargent has published a good Dockerfile cheat sheet.

Moving forward

Creating new Docker images that can be used by you or by other developers is pretty straightforward.  You have the option to manually create and commit changes, or to script them using a Dockerfile. Once your images are created, you can orchestrate your containers in large-scale, dynamic deployments using Kubernetes. 

In previous guides, we looked at how to deploy Kubernetes and create a cluster. We also looked at how to deploy an application on the cluster environment and configure OpenStack instances (including security) to help you access them. 

In our next tutorial, we’ll look at using YAML to manage containers with Kubernetes.

FREE EBOOK!
Service Mesh for Mere Mortals
A Guide to Istio and How to Use Service Mesh Platforms
DOWNLOAD
LIVE WEBINAR
Secure & Accelerate Your Container DevOps Lifecycle

Presented with Carbonetes
SAVE SEAT
LIVE WEBINAR
Hyperscale your Data Center with Guaranteed Outcomes

Tuesday, December 14 at 9:00 am EST
SAVE SEAT
LIVE WEBINAR
Cloud Native & Coffee: Is Open Source Only About Free Software?

Thursday, December 16 at 8:00 am PST
SAVE SEAT
Mirantis Webstore
Purchase Kubernetes support
SHOP NOW
Technical training
Learn Kubernetes & OpenStack from Deployment Experts
Prep for certification!
View schedule
WHITEPAPER
The Definitive Guide to Container Platforms
READ IT NOW
FREE EBOOK!
Service Mesh for Mere Mortals
A Guide to Istio and How to Use Service Mesh Platforms
DOWNLOAD
LIVE WEBINAR
Secure & Accelerate Your Container DevOps Lifecycle

Presented with Carbonetes
SAVE SEAT
LIVE WEBINAR
Hyperscale your Data Center with Guaranteed Outcomes

Tuesday, December 14 at 9:00 am EST
SAVE SEAT
LIVE WEBINAR
Cloud Native & Coffee: Is Open Source Only About Free Software?

Thursday, December 16 at 8:00 am PST
SAVE SEAT
Mirantis Webstore
Purchase Kubernetes support
SHOP NOW
Technical training
Learn Kubernetes & OpenStack from Deployment Experts
Prep for certification!
View schedule
WHITEPAPER
The Definitive Guide to Container Platforms
READ IT NOW