3. Docker Images

In this lab, we will write a Dockerfile and build a new Docker image.

Chapter Details
Chapter Goal Learn how to write Dockerfile and build Docker image
Chapter Sections

3.1. Build a Docker image

In the previous section (2. Docker Concepts), we learned how to use Docker images to run Docker containers. Docker images that we used have been downloaded from the Docker Hub, a registry of Docker images. In this section we will create a simple web application from scratch. We will use Flask (http://flask.pocoo.org/), a microframework for Python. Our application for each request will display a random picture from the defined set.

In the next session we will create all necessary files for our application. The code for this application is also available in GitHub:

3.1.1. Create project files

Step 1 Create a new directory flask-app for our project:

$ mkdir flask-app
$ cd flask-app

Step 2 In this directory, we will create the following project files:

flask-app/
    Dockerfile
    app.py
    requirements.txt
    templates/
        index.html

Step 3 Create a new file app.py with the following content:

from flask import Flask, render_template
import random

app = Flask(__name__)

# list of cat images
images = [
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Combing-Itself.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Hug.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Playing-Basketball.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Playing-Ping-Pong.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Using-Chopsticks.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Using-Computer.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-Wearing-Glasses.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-With-Beer.jpg",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cat-With-Teddy-Bear.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cats-Sitting-Like-Human.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cats-Using-iPad.gif",
"https://bitbucket.org/mirantis-training/public/raw/master/gifs/cats/Cats-Wearing-Party-Hats.gif"
]

@app.route('/')
def index():
    url = random.choice(images)
    return render_template('index.html', url=url)

if __name__ == "__main__":
    app.run(host="0.0.0.0")

Step 4 Create a new file requirements.txt with the following line:

Flask==0.10.1

Step 5 Create a new directory templates and create a new file index.html in this directory with the following content:

<html>
  <head>
    <style type="text/css">
      body {
        background: black;
        color: white;
      }
      div.container {
        max-width: 500px;
        margin: 100px auto;
        border: 20px solid white;
        padding: 10px;
        text-align: center;
      }
      h4 {
        text-transform: uppercase;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h4>Cat Gif of the day</h4>
      <img src="{{url}}" />
    </div>
  </body>
</html>

Step 6 Create a new file Dockerfile:

# our base image
FROM alpine:3.5

# Install python and pip
RUN apk add --update py2-pip

# upgrade pip
RUN pip install --upgrade pip

# install Python modules needed by the Python app
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r /usr/src/app/requirements.txt

# copy files required for the app to run
COPY app.py /usr/src/app/
COPY templates/index.html /usr/src/app/templates/

# tell the port number the container should expose
EXPOSE 5000

# run the application
CMD ["python", "/usr/src/app/app.py"]

3.1.2. Build a Docker image

Step 1 Now we can build our Docker image. In the command below, replace <user-name> with your user name. For a real image, the user name should be the same as you created when you registered on Docker Hub. Because we will not publish our image, you can use any valid name:

$ docker build -t <user-name>/myfirstapp .
Sending build context to Docker daemon 8.192 kB
Step 1 : FROM alpine:3.5
...
Successfully built f1277efd5a79

Step 2 Check that your image exists:

$ docker images
REPOSITORY     TAG    IMAGE ID     CREATED       SIZE
.../myfirstapp latest f1277efd5a79 6 minutes ago 56.34 MB

Step 3 Run a container in a background and expose a standard HTTP port (80), which is redirected to the container’s port 5000:

$ docker run -dp 80:5000 --name myfirstapp <user-name>/myfirstapp

Step 4 Use your browser to open the address http://<lab IP> and check that the application works.

Step 5 Stop the container and remove it:

$ docker stop myfirstapp
myfirstapp
$ docker rm myfirstapp
myfirstapp

3.2. Build a multi-container application

In this section, will guide you through the process of building a multi-container application. The application code is available at GitHub:

Step 1 Clone the existing application from GitHub:

$ git clone https://github.com/dockersamples/example-voting-app
Cloning into 'example-voting-app'...
...

$ cd example-voting-app

Step 2 Edit the vote/app.py file, change the following lines near the top of the file:

option_a = os.getenv('OPTION_A', "Cats")
option_b = os.getenv('OPTION_B', "Dogs")

Step 3 Replace “Cats” and “Dogs” with two options of your choice. For example:

option_a = os.getenv('OPTION_A', "Java")
option_b = os.getenv('OPTION_B', "Python")

Step 4 The existing file docker-compose.yml defines several images:

  • A voting-app container based on a Python image
  • A result-app container based on a Node.js image
  • A Redis container based on a redis image, to temporarily store the data.
  • A worker app based on a dotnet image
  • A Postgres container based on a postgres image

Note that three of the containers are built from Dockerfiles, while the other two are images on Docker Hub.

Step 5 Let’s change the default port to expose. Edit the docker-compose.yml file and find the following lines:

...
ports:
  - "5000:80"
...

Change 5000 to 80:

...
ports:
  - "80:80"
...

Step 6 Install the docker-compose tool:

$ sudo ~/k8s-examples/install/install-compose.sh

Step 7 Use the docker-compose tool to launch your application:

$ docker-compose up -d

Step 8 Check that all containers are running:

$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                          NAMES
e29d586fa86f        examplevotingapp_worker   "/bin/sh -c 'dotne..."   21 seconds ago      Up 20 seconds                                                      examplevotingapp_worker_1
5fdad7b26fd3        postgres:9.4              "docker-entrypoint..."   24 seconds ago      Up 23 seconds       5432/tcp                                       db
145d0d583fb3        examplevotingapp_vote     "python app.py"          24 seconds ago      Up 20 seconds       0.0.0.0:80->80/tcp                             examplevotingapp_vote_1
7cc71a6c4bd6        redis:alpine              "docker-entrypoint..."   24 seconds ago      Up 23 seconds       0.0.0.0:32771->6379/tcp                        redis
ba4408f65906        examplevotingapp_result   "nodemon server.js"      24 seconds ago      Up 20 seconds       0.0.0.0:5858->5858/tcp, 0.0.0.0:5001->80/tcp   examplevotingapp_result_1

Check the NAMES of the created containers. As you can see the container names are prefixed by the project name examplevotingapp_ and postfixed by the container instance id, except in the cases where container_name: was specified in the docker-compose.yml file.

Step 9 Use your browser to open the address http://<lab IP> and check that the application works by voting for one of the languages.

Check the result of you vote using the exposed port of the result container/service by going to http://<lab IP>:5001.

Looks like our code change has a bug - do you see it? The result is still showing votes for CATS vs. DOGS.

Step 10 Stop the application:

$ docker-compose down
Stopping db ... done
Stopping redis ... done
...

Check to see if docker-compose cleaned up terminated containers:

docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

docker-compose down adds additional semantics where containers for terminated applications are automatically cleaned up.

Checkpoint

  • Write Dockerfile
  • Build an image
  • Install Docker Compose
  • Write docker-compose.yml file
  • Use Docker Compose to build and run a multi-container application