/ Deployment

Getting Started with Docker Guide with some Docker Usage Examples

From our Previous Post we have gone through an overview of Docker and setting up Docker on Ubuntu 16.04.

In this guide we will be going through some Docker Examples, a hands-on guide.

Docker Hub:

Docker hub is a public repository that hosts images, you instead of installing MySQL for example, you can search for MySQL images on docker hub, and create containers from them.

But just as a side note, there's a LOT of images, and anyone can upload images to Docker Hub, its always a good practice, when launching a container, to see what is in the container just to make sure everything appears as they should be.

Examples, Examples and more Examples:

The will go through the following examples that I found helped me understand Docker from a "learn-as-you-do-experience":

Search for a Base Image:

In our example we will search for a small image, named busybox:

$ docker search busybox
NAME                            DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
busybox                         Busybox base image.                             1014      [OK]

Now that we located our image that is hosted on Docker Hub, we will pull down the image to our local cache then we will instantiate an instance from our image.

$ docker run -it busybox sh
/ # 

Run a Container From a Image:

Note that while we are in the shell of this container, as soon as we exit this instance, any data that was created on the container will be lost, lets have a look at an example:

$ docker run -it busybox sh
/ # touch /root/test.txt
/ # ls /root/ | wc -l
1
/ # exit

Run another container from our image, and you will find that the data will be missing that was created above:

$ docker run -it busybox sh
/ # ls /root/ | wc -l
0
/ # exit

Now in order to persist data, you could look at a couple of options that includes:

  • You launch a container, you work on the cotainer, exit, and commit the changes to a image, then you also have the option to push it to a registry of choice.

  • You could mount the directory to the host.

  • Data Container Volumes.

Committing Changes to your image:

Let's create a container from the busybox image, and then create a file under the root partition:

$ docker run -it busybox sh
/ # echo "foo" > /root/data.txt
/ # exit

Let's first have a look at the images that we currently have stored locally on the docker host:

$ docker images
REPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE
busybox                                         latest              00f017a8c2a6        3 months ago        1.11MB

We can see that we have one image stored. In order to have an image that contains the data that we produced on our container, we need to commit these changes to a image and we can set a tag as an identifier.

The first thing would be to get the container id, that we can achieve by running docker ps, but we would like to filter the output to only show the latest container that ran, that includes all states (Created, Exited, or Running):

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
ae8d125e1b2b        busybox             "sh"                26 minutes ago      Exited (0) 3 seconds ago                       gallant_goldwasser

Committing the changes to our new image:

$ docker commit ae8d125e1b2b mybusybox:v1
sha256:5bc7e5e074c8837964d99721ed36ff46dffe3f52259db8b14b6ccf53e29131ed

Have a look at our images:

$ docker images
REPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE
mybusybox                                       v1                  5bc7e5e074c8        16 seconds ago      1.11MB
busybox                                         latest              00f017a8c2a6        3 months ago        1.11MB

Running a container from our created image:

$ docker run -it mybusybox:v1 sh
/ # cat /root/data.txt
foo

We can also run our container in a detached mode, so when we exit our container, that it keeps running in the background. Optionally set a name for our container using the --name option:

$ docker run --name container01 -itd mybusybox:v1 sh
030244a7edaa1d7eb09915c1c5d8f85ca55c4013f4456a040f8cf75fcf834758

List the running containers:

$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                                                                              NAMES
030244a7edaa        mybusybox:v1         "sh"                     3 minutes ago       Up 3 minutes                                                                                           container01

Entering the Shell from our running container:

You can enter the shell either via passing the container id or the container name:

  • Container ID:
$ docker exec -it 030244a7edaa sh
/ #

  • Container Name:
$ docker exec -it container01 sh
/ #

Executing Commands on a Container directly from the Docker Host:

$ docker exec -it 030244a7edaa ifconfig eth0 | head -2
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:09
          inet addr:172.17.0.9  Bcast:0.0.0.0  Mask:255.255.0.0

Data Volumes:

A data volume has a reserved directory within one or more containers that bypasses the union file system. One of the advantages that data volumes has to offer, is that multiple containers can share the same data volume.

Let's create a data volume called myvol01:

$ docker volume create --driver local myvol01
myvol01

Listing the volumes on our Docker Host:

$ docker volume list | grep -E '(DRIVER|myvol)'
DRIVER              VOLUME NAME
local               myvol01

In order to view information about our volume, we use the inspect option on our volume:

$ docker volume inspect myvol01

[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/myvol01/_data",
        "Name": "myvol01",
        "Options": {},
        "Scope": "local"
    }
]

Create a Container with the Data Volume:

Creating a container using the data volume that we created:

$ docker run --name container01 -v myvol01:/opt -itd mybusybox:v1 sh

Inspecting the configuration of our container:

$ docker inspect container01

[
    {
        "Id": "aac1cad1e8f4db10ea5d8f64f2198b51070bcccfb4400b0b8677bda27595292d",
        "Created": "2017-06-09T23:54:39.803934979Z",
        "Path": "sh",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "StartedAt": "2017-06-09T23:54:40.485994777Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "",
        "ResolvConfPath": "/var/lib/docker/foo/bar/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/foo/hostname",
        "HostsPath": "/var/lib/docker/containers/foo/hosts",
        "LogPath": "/var/lib/docker/containers/foo/bar-json.log",
        "Name": "/container02",
        "HostConfig": {
            "Binds": [
                "myvol01:/opt"
            ],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "ConsoleSize": [
                0,
                0
            ],
..
        },
        "Mounts": [
            {
                "Type": "volume",
                "Name": "myvol01",
                "Source": "/var/lib/docker/volumes/myvol01/_data",
                "Destination": "/opt",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
            }
        ],
        "Config": {
            "Hostname": "aac1cad1e8f4",
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "sh"
            ],
            "Image": "mybusybox:v1",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "Ports": {},
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.10",
            "MacAddress": "02:42:ac:11:00:0a",
            "Networks": {
                "bridge": {
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.10",
                    "IPPrefixLen": 16,
                    "MacAddress": "02:42:ac:11:00:0a"
                }
            }
        }
    }
]

We can create a second container using the same volume, and you will find that both containers shares the same volume, which the path is mapped to:

$ docker run --name container02 -v myvol01:/opt -itd mybusybox:v1 sh

More information on Docker Volumes can be found on their docs

Building an Image from a Dockerfile:

A Dockerfile is like a set of instructions that defines how your expected environment inside your container should look like.

A Dockerfile will be created in a new directory, including all the needed files that our Dockerfile will be referencing. For this example, we will be creating a simple Python Flask app that just responds on a HTTP GET Request.

Create The Directory:

$ mkdir flask-app
$ cd flask-app

Create the Python Flask App:

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello from your Docker Container'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Create the Requirements File:

requirements.txt

flask

Create the Dockerfile:

Dockerfile

# use an official python runtime as a base image
FROM python:2.7-slim

# set the working directory to /app
WORKDIR /app

# copy the directory contents into the container to /app
ADD . /app

# install the dependencies from our requirements file
RUN pip install -r requirements.txt

# expose port 5000 outside the container
EXPOSE 5000

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

Build the Image:

We can now build the image by passing a tag name, the example output that I will provide will be a filtered version just so that you can see how it more or less looks like:

$ docker build -t flask-app:v1 .
Sending build context to Docker daemon  4.096kB
Step 1/6 : FROM python:2.7-slim
2.7-slim: Pulling from library/python
ef0380f84d05: Pull complete
4a615537653d: Pull complete
dd25cca6a171: Pull complete
860f8782a0f0: Pull complete
Step 2/6 : WORKDIR /app
Step 3/6 : ADD . /app
Step 4/6 : RUN pip install -r requirements.txt
Collecting flask (from -r requirements.txt (line 1))
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 Werkzeug-0.12.2 click-6.7 flask-0.12.2 itsdangerous-0.24
Step 5/6 : EXPOSE 5000
Step 6/6 : CMD python app.py
Successfully built edbfd6c89474
Successfully tagged flask-app:v1

Listing our image:

docker images | grep -E '(REPO|flask-app)'
REPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE
flask-app                                       v1                  edbfd6c89474        22 seconds ago      194MB

Running a Container from Our Image:

$ docker run --name flaskapp -itd flask-app:v1

Listing our Container:

$ docker ps | grep -E '(CONTAINER|flaskapp)'
CONTAINER ID        IMAGE                COMMAND                  CREATED              STATUS              PORTS                                                                              NAMES
a4584e5f8181        flask-app:v1         "python app.py"          About a minute ago   Up About a minute   5000/tcp   

As we have exposed port 5000, we need to determine the IP Address of our Container, that can be done by doing the following:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' flaskapp
172.17.0.12

Now, to test our web application, lets make a GET Request to our Endpoint:

$ curl -XGET http://172.17.0.12:5000/
Hello from your Docker Container

We can also do port forwarding, by binding port 80 to the containers port which is 5000, let's first delete our container:

$ docker rm flaskapp -f
flaskapp

Now that the container has been deleted, lets create that container again, but this time we will bind port 80 of our docker host, to port 5000 of our container, so that we can enable clients from outside our Docker Host to reach the endpoint:

$ docker run --name flaskapp -itd -p 80:5000 flask-app:v1

Now if we list the container we will see the port configuration shows that the port the host port 80 will be translated to port 5000 of the container:

$ docker ps | grep -E '(CONTAINER|flaskapp)'
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                                                                              NAMES
c4ef4da4b677        flask-app:v1         "python app.py"          15 seconds ago      Up 14 seconds       0.0.0.0:80->5000/tcp                                                               flaskapp

To test it out, first get our IP Address of our Docker Host:

$ ifconfig eth0 | grep Bcast | awk '{print $2}' | cut -d':' -f2
10.4.116.69

Finally, making the GET request to our Docker Host:

$ curl -XGET http://10.4.116.69
Hello from your Docker Container

I hope this was useful, if there is any feedback or question, please feel free to leave a comment below.