Beginning of Docker

Content

Motivation

Before I actually use Docker, the only thing I know about Docker is that it is a tool to create a box that make sure an application can be run over everyone’s machine. I don’t even know why we need that box and what’s really going on in the box.

After I worked as a Data Engineer, I started to know the importance of dependencies in a project or application. If we would like to make everyone contribute a project smoothly, it would be better to force everyone under the same environment or dependencies. I will talk about the dependency maintenance in another blog.

Let’s switch back to Docker. The motivation of Docker is to provide a project or an application with the same environment and dependency. Every one has their own machines with different operating systems(MacOS, Windows, Linux), different version of the system even different setting. Each little different components may cause failure of an application. To resolve this issue, Docker comes to live. Docker offers an simple way to build and run an application without thinking about much lower level setting or system diversity issue.

Basic Concept

Here are some basic concepts frequently used as a client.

Docker Images:

When I was a a child, the old Windows time, I always heard someone said they had the image to help others reinstall the system. That image refers a disk image, a replica of the contents fo a storage device stored on a second storage device. That image can reset and install a brand new system. We can think about docker image in a similar way. A docker image can set and install a ‘system’(environment) for an application every time you run the application.

Docker image is a read-only template with instructions for creating a Docker Container, which means you can record the way how you set up the environment for the application for the container setting in a template file called Dockerfile using Docker syntax.

Often, an image is based on another image an some additional customization. For example, we can include a python image, commands to prepare some required dependencies and commands to start a flask app. With this docker image, every one can run a flask app without considering environment issue.

One interesting property of docker image is that each instruction will creates a layer and get cached from top to the bottom. When we rebuild the image, the process is fast since each layer gets cached. But when we change some instructions and rebuild the image, the process will recreate the layers from the first instruction we change to the bottom instruction. Sometimes a large image can be build for more than 5 minutes, with this property, we know the best practice is to put the instruction with lower chances changing on the top.

Docker Container

Docker Container is a runnable instance of image. We can create, start, stop, move or delete using Docker CLI. We can connect one or more networks and attach storage to it.

Architecture

Docker uses a client-server architecture as follow,

Docker Architecture

Here are some other concepts in this architecture graph.

Docker Deamon:

Docker Deamon is used to listen client API requests, manage docker object like container, images, network, volume and communicate with other deamons to manage docker service.

Docker Registry(Docker Hub)

Docker Hub is the most typical docker registry. We can image Docker Hub is a public library holding enormous amount of images in remote side. Every one can push their own images into Docker Hub and pull others’ images from Docker Hub for their own use.

Explanation:

In the graph, there are three types of lines representing how Docker deamon reacts with those three client commends.

  • When we run docker build, the images will be created in Docker deamon
  • When we run docker pull, the required images can be pulled from the configured registry.
  • When we run docker run, the images will be created and then send the image into container to spawn up the application

Apart from the above commands, when we run docker push, we can push our image to the configured registry.

Example

Let’s go through an official docker example to understand how we can use docker to deploy our own application. I will explain what underlying process happening with each step.

Prepare project

First we create our project in the following folder structure

flask_sample/
    templates/
        index.html
    .tool-versions
    app.py
    Dockerfile
    poetry.lock
    pyproject.toml  

The tool-versions comes from asdf, a runtime version management package.

Also, pyproject.toml and poetry.lock comes from poetry, This way is better than using requirements.txt in a project. I will cover details in another blog about dependency management.

In the index.html, we can use the following code, it just define the layout of the web page

<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}}" />
      <p><small>Courtesy: <a href="https://www.buzzfeed.com/copyranter/the-best-cat-gif-post-in-the-history-of-cat-gifs">Buzzfeed</a></small></p>
    </div>
  </body>
</html>

Then we add app.py file into the project folder, it’s the script define the application functionality and connect the application with IP 0.0.0.0.

from flask import Flask, render_template
import random

app = Flask(__name__)

# list of cat images
images = [
   "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26388-1381844103-11.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr01/15/9/anigif_enhanced-buzz-31540-1381844535-8.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26390-1381844163-18.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr06/15/10/anigif_enhanced-buzz-1376-1381846217-0.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr03/15/9/anigif_enhanced-buzz-3391-1381844336-26.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr06/15/10/anigif_enhanced-buzz-29111-1381845968-0.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr03/15/9/anigif_enhanced-buzz-3409-1381844582-13.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr02/15/9/anigif_enhanced-buzz-19667-1381844937-10.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26358-1381845043-13.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr06/15/9/anigif_enhanced-buzz-18774-1381844645-6.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr06/15/9/anigif_enhanced-buzz-25158-1381844793-0.gif",
    "https://img.buzzfeed.com/buzzfeed-static/static/2013-10/enhanced/webdr03/15/10/anigif_enhanced-buzz-11980-1381846269-1.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")

Write Dockerfile

Now, let’s create our Dockerfile. All we need to add in Dockerfile is sequentially write down the commands to build up the environment and dependency.

FROM python:3.7.4-slim

Our base image is python 3.7.4. On Docker Hub, there is lots of python images available. We pick up python:3.7.4-slim image.

WORKDIR /usr/src/app

Declare the work directory in the container. In container we will have the directory /usr/src/app.

ENV FLASK_APP=app.py

We export the flask environment into the container.

RUN pip install --upgrade pip
RUN pip install poetry==0.12.17

Since the python image only contains pip. So to run poetry command, we need to use pip install poetry in the container.

ADD pyproject.toml .
ADD poetry.lock .

We copy our dependency management file into the container.

RUN poetry install --no-dev

Since we have already have the pyproject.toml and poetry.lock files in the container. Now we can install the dependencies in the container. Flag --no-dev means we only install dependencies unrelated to develop use. After this command, the container now holds all dependencies we need.

COPY app.py /usr/src/app/
COPY templates/index.html /usr/src/app/templates/

We prepare all required files in the container work directory to run the application.

EXPOSE 5000

We tell the container expose on port number 5000.

CMD ["poetry", "run", "flask", "run", "--host=0.0.0.0"]

In the container, we run the flask app. This is the special syntax to run command inside container. The command is separated by comma for each keyword.

We all set now.

The whole script of Dockerfile:

FROM python:3.7.4-slim

WORKDIR /usr/src/app

ENV FLASK_APP=app.py

RUN pip install --upgrade pip
RUN pip install poetry==0.12.17

ADD pyproject.toml .
ADD poetry.lock .

RUN poetry install --no-dev

COPY app.py /usr/src/app/
COPY templates/index.html /usr/src/app/templates/

EXPOSE 5000

CMD ["poetry", "run", "flask", "run", "--host=0.0.0.0"]

Build Docker Image

docker build -t <user_name>/myfirstapp .

Flag -t means tag my image with a name. Now the Docker deamon holds the image you build. We can check image using docker image ls to see what image is available in our Docker deamon.

Run Image

docker run -p 5000:5000 --name myfirstapp <user_name>/firstapp

Flag -p means port. With this flag, we can plugin the local port with the docker application port. --name is actually the application name. The finally argument is the image name.

We can check the status of docker application container by running docker ps. docker ps -a will show all applications run by docker including closed applications

Awesome, now we learn how to run a docker application now. So how to make other people run my application in other machine? Making sure everyone can run an application conveniently is the point of Docker. One way is that we can push our image into Docker Hubso that others can fetch our image from Docker Hub by running docker push <user_name>/myfirstapp.

Some other useful commands:

docker stop myfirstapp

to stop the application container.

docker rm myfirstapp

to delete the application container.

Summary

The above content is basically what I have tried and learned at the first time. Hopefully this can help understanding at the very beginning of the journey of Docker. This blog is only the tip of iceberg of Docker. Some of the content is referenced from official documentation. I highly recommend reading the official documentation for more advanced details.

comments powered by Disqus