Creating Containers

Docker
Composing

In the previous section we started with an Ubuntu image. This is a pretty big docker image to start with and using such an image for a micro-service in an application can lead to inefficiency and other problems. You always want to think as containers starting from the bare-minimum requirements, in other words. It is best to start from the ground up.

For this reason there are various container distributions that are very small and contain just the bare requirements to run. One of these is called Alpine linux.

If you go to Alpine Repository in Docker Hub ( https://hub.docker.com/_/alpine/ ) you will start to see significant differences between Alpine and Ubuntu. For example if you click on the Tags you will notice that the compressed size is around 2MB ( compared to Ubuntu's 43MB ). Using these small containers will lead to smaller download times from registries, easier manipulation and a more secure environment ( less processes running in the containers ). Yet the challenge with these containers is, because they have been brought down to a bare minimum, they are not well suited for human interaction. They miss many of the amenities of a normal Linux installation ( e.g. Alpine doesn't even include bash, only sh )

But these provide great foundations of simplicity on which we can build a container that performs the specific function you are looking for and nothing else.

Step 1 - Download Alpine image


docker pull alpine:3.10

Running the command docker images will give you an immediate view of the different between these two containers as it relates to size.


docker images

[root@pod09-master ~]# docker images
REPOSITORY      TAG     IMAGE ID        CREATED         SIZE
alpine          3.10    3fd9065eaf02    2 days ago      5.56MB
ubuntu          16.04   96da9143fb18    10 days ago     124MB

Step 2 - Modify Alpine image with new software

Now that we have donwloaded the Alpine image, we will be making some modifications to the image to our tastes. To start this container we are going to take a slightly different approach and not start in detached mode and execute a shell (sh) and drop directly into the container.


docker run -t -i -h=alpine-ciscolive --name alpine-ciscolive alpine:3.10 sh

This will leave you directly in the container. Now you will install additional packages that we need inside this container. We want to install a series of packages that we want to add.


apk add --update --no-cache python3

Which will add the packages for Alpine linux for python3.

/ # apk add --update --no-cache python3
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/11) Installing libbz2 (1.0.6-r7)
(2/11) Installing expat (2.2.8-r0)
(3/11) Installing libffi (3.2.1-r6)
(4/11) Installing gdbm (1.13-r1)
(5/11) Installing xz-libs (5.2.4-r0)
(6/11) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(7/11) Installing ncurses-terminfo (6.1_p20190518-r0)
(8/11) Installing ncurses-libs (6.1_p20190518-r0)
(9/11) Installing readline (8.0.0-r0)
(10/11) Installing sqlite-libs (3.28.0-r2)
(11/11) Installing python3 (3.7.5-r1)
Executing busybox-1.30.1-r3.trigger
OK: 70 MiB in 25 packages

You can then upgrade PIP in the container.


pip3 install --upgrade pip

/ # pip3 install --upgrade pip
Collecting pip
  Downloading https://files.pythonhosted.org/packages/54/0c/d01aa759fdc501a58f431eb594a17495f15b88da142ce14b5845662c13f3/pip-20.0.2-py2.py3-none-any.whl (1.4MB)
     100% |=========================================================================================| 1.4MB 2.4MB/s
Installing collected packages: pip
  Found existing installation: pip 19.2.3
    Uninstalling pip-19.2.3:
      Successfully uninstalled pip-19.2.3
Successfully installed pip-20.0.2

Now, that we have succesfully completed the installation of the Alpine packages and upgraded PIP. You will exit the container in order to continue to the next task. Where we will be modifying the image.


exit

Step 3 - Create image based on container changes

Now that you have made the changes to the container, we can make a new container image based on those changes.


docker commit -m "Added Python to Alpine" alpine-ciscolive ciscolive/alpine-python

[root@pod09-master ~]# docker commit -m "Added Python to Alpine" alpine-ciscolive ciscolive/alpine-python

sha256:24a5136782451cf7ad815b4ebfb3c8c07216dff6214837f01dd8f31124b099db

You will notice that the command returned a string of characters with sha256. This means that the commit was succesful. It is time to verify that the image was created by runinng by following command docker images.


docker images

And the output should be similar to:

"[root@pod09-master ~]#" docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
ciscolive/alpine-python   latest              faf48dff46c6        17 seconds ago      60.7MB
alpine                    3.10                af341ccd2df8        2 days ago          5.56MB
ubuntu                    16.04               96da9143fb18        10 days ago         124MB

Now you have created a new container image that contains Alpine and the base python3 code base to run a python script. You will notice that the image size has increased to a size of 60.6MB. That was just a simply install of python. Now imagine basing that in an image that is bigger. If you aren't paying attention container images can grow to a point that they are no different than virtual machines, bloated with things that are not necessary for the proper execution of your micro-service application.

With this container that you have built you could simply now run the container and it will have the python3 already installed. While this was a interesting exercise, there is a much better way to accomplish this goal using what is known as a Dockerfile.

Dockerfile

Dockerfile is a instruction set for building a docker image. The advantage of Dockerfile is that a text file can be used to define the exact construct of the container and how that container is to interact with the host operating system, file system and network components. Many applications use Dockerfiles as the mechanism to define how a series of containers interact with each other to build an application of micro-services.

The file is composed of a series of instructions. A couple of these are:

COMMAND DESCRIPTION
FROM Defines the base image for the container build and subsequent instructions. In our case we would start with the alpine image and then add to it.
RUN The RUN command will execute the commands in the new R/W layer that is created for the container.
CMD There can only be one CMD defined in a Dockerfile. If you list more than one, only the last one will take effect. The main purpose of CMD is to provide defaults for an executing container.
EXPOSE This instruction informs Docker that the container listens on the specified ports at runtime.
COPY Copies files from a directory into the container filesystem R/W layer.
VOLUME Creates a mount point from the native host. Provides a mechanism to have a view onto the host container filesystem.

For a complete reference on Dockerfile please check the Reference Documentation at Docker.

Step 4 - Create Dockerfile

As the first step you will create a directory for us to work with.


cd ~
mkdir dockerplay
cd dockerplay

Inside the directory you will create a new Dockerfile. We have simplified this process for you to easily create the file with a simple copy/paste.


cat << EOF > Dockerfile
FROM alpine:3.10

LABEL description="Alpine Linux with Python 3"
LABEL version="1.0"
LABEL maintainer="pod09-ltraci2967@ciscolive.com"

RUN apk add --update --no-cache python3
RUN pip3 install --upgrade pip
EOF

With the dockerfile built, now you can execute the docker build command.


docker build -t ciscolive/dockerfile-example .

For which the output should look similar:

[root@pod09-master dockerplay]# docker build -t ciscolive/dockerfile-example .
Sending build context to Docker daemon  2.048kB
Step 1/6 : FROM alpine:3.10
 ---> af341ccd2df8
Step 2/6 : LABEL description="Alpine Linux with Python 3"
 ---> Running in be765cee0545
Removing intermediate container be765cee0545
 ---> c31b2b8fe8b2
Step 3/6 : LABEL version="1.0"
 ---> Running in 0386b0ebce04
Removing intermediate container 0386b0ebce04
 ---> 10ebb97b4737
Step 4/6 : LABEL maintainer="pod16-ltraci2967@ciscolive.com"
 ---> Running in 23a2c117aa43
Removing intermediate container 23a2c117aa43
 ---> 2be730db9823
Step 5/6 : RUN apk add --update --no-cache python3
 ---> Running in f15ef49aaed9
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/11) Installing libbz2 (1.0.6-r7)
(2/11) Installing expat (2.2.8-r0)
(3/11) Installing libffi (3.2.1-r6)
(4/11) Installing gdbm (1.13-r1)
(5/11) Installing xz-libs (5.2.4-r0)
(6/11) Installing ncurses-terminfo-base (6.1_p20190518-r0)
(7/11) Installing ncurses-terminfo (6.1_p20190518-r0)
(8/11) Installing ncurses-libs (6.1_p20190518-r0)
(9/11) Installing readline (8.0.0-r0)
(10/11) Installing sqlite-libs (3.28.0-r2)
(11/11) Installing python3 (3.7.5-r1)
Executing busybox-1.30.1-r3.trigger
OK: 70 MiB in 25 packages
Removing intermediate container f15ef49aaed9
 ---> e3133834983d
Step 6/6 : RUN pip3 install --upgrade pip
 ---> Running in 41ec9a7f6bc1
Collecting pip
  Downloading https://files.pythonhosted.org/packages/54/0c/d01aa759fdc501a58f431eb594a17495f15b88da142ce14b5845662c13f3/pip-20.0.2-py2.py3-none-any.whl (1.4MB)
Installing collected packages: pip
  Found existing installation: pip 19.2.3
    Uninstalling pip-19.2.3:
      Successfully uninstalled pip-19.2.3
Successfully installed pip-20.0.2
Removing intermediate container 41ec9a7f6bc1
 ---> 1b1cb09d8a18
Successfully built 1b1cb09d8a18
Successfully tagged ciscolive/dockerfile-example:latest

You can run docker images and you will see a new image has been built.


cd ~
docker images

[root@pod09-master ~]#docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED              SIZE
ciscolive/dockerfile-example   latest              1b1cb09d8a18        About a minute ago   68.3MB
ciscolive/alpine-python        latest              faf48dff46c6        2 minutes ago        60.7MB
alpine                         3.10                af341ccd2df8        2 days ago           5.56MB
ubuntu                         16.04               96da9143fb18        10 days ago          124MB