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.
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
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
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 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.
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