Welcome to my tutorials on how to leverage Docker to deploy your application! This is meant as much to help me learn, so it doubles as a “what worked for me” series.
In the previous lesson, we covered how to add files to an image and run them. This included the file that was ran calling other files that were uploaded.
This lesson, we are going to add a package to the image and setup Docker to launch that software when it creates a container.
Packages are Operating System specific bundles of software. Depending on the version of Linux you run, you could leverage RPM, DEB, or APK packages to deploy new programs to your computer. Containers, since they are based on an operating system, have the ability to install packages using the base OS package manager. For both Windows- and Linux-based containers, software can also be added to an image by running an installation executable in the Dockerfile.
To get started, you must know how to use the underlying package manager for the base OS you plan to use for your Dockerfile. For this tutorial, I will use the ubuntu/latest image, as I am comfortable with Ubuntu package management (and I don’t feel like learning how Alpine Linux manages software today). Most distributions make it easy to install the software you want; each package identifies the packages it need to run correctly. To take advantage of this fact, we will create an image that contains Python 3. We will then create a second image that uses the first to write your running “Hello world!” to console.
Before we go creating Docker files, let’s play with the Ubuntu image first, shall we? Run the following:
docker run -it ubuntu
This will drop you into a bash shell, even on Windows (that’s slick). Note that the user is root, and the host name is a random alphanumeric value. That value is the container ID, which you can get with the commands:
docker ps docker conatiner ls
This bash shell is an Ubuntu bash shell, and by default has the core Ubuntu software installed, including the apt package management suite. from here, we can test a few commands to see how to install the software we want into our image. If you are looking to install a specific piece of software from the package management software, you can usually find the package you want to install with a well-formed Google search. Since we want to install Python 3, the following should work if my memory serves me:
apt-get -y install python3
Wait, what’s this?
root@ce0b6cd38d85:/# apt-get -y install python3
Reading package lists… Done
Building dependency tree
Reading state information… Done
E: Unable to locate package python3
root@ce0b6cd38d85:/#
That package can’t be found. However, the reason why is very simple: we haven’t updated the package manager with what packages are available. So we need to run the following command first:
apt-get update
A list of web servers will spring to life as apt-get searches for what’s out there. It will take a while too: Ubuntu has a lot of good software to choose from, and we are downloading the entire catalog of it.
When that’s done, rerun the apt-get install command that failed the first time, and it will install Python 3 into our container. Note that this is an important point: the container is your base image, plus a “working” layer that tracks your running changes beyond the initial source image. If you stop and restart the container, you will lose no data. But if you destroy and recreate your container, you will lose all your changes and have to start from ground zero. You can, however, convert your container to an image, but then your “working” layer becomes a permanent part of your image, and you potentially introduce bloat into your image. But there are ways to work around that. 🙂
Let’s test our python installation out:
echo 'print ("Hello world!")' | python3
If you get “Hello world!” again, then Python 3 installed correctly into our container. Now let’s make this into an image. When you’re ready, run “exit” to leave your docker container, and you can remove it if you would like:
docker container ls -a docker container rm <container_id_or_name>
Create an empty folder for your python image. In there, create a Dockerfile with the following:
FROM ubuntu RUN apt-get update && apt-get install -y python3 CMD [ "python3" ]
This introduces the RUN command. RUN takes the place of you connecting to the shell in your container and typing in the commands. Reading the Docker documantation at https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run, they indicate that it is best practice to join your run commands into one RUN call, as each RUN call generates an intermediary image layer.
It is important to point out that whatever you do with the RUN command stays in your image, including any temporary files that you do not want to be a part of your image. Our particular RUN command will download the entire Ubuntu package catalog and LEAVE IT IN YOUR IMAGE. You are responsible to clean up after any RUN command you use. If you want to clean up in this case, you can substitute the following RUN command in your Dockerfile, which adds a command to clean the package cache:
RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/*
Now change your directory to where your Dockerfile is at and let’s build and run this image:
docker build -t my_python . docker run -it --rm my_python
The CMD satement causes the python shell to launch when the image is run. You can run the following python command:
print ("Hello world!")
and Python obediently echoes the string on the console. To get out, hit Control-D then enter.
Because it helps to understand why cleanup is important, I created two Dockerfiles, each using one of the two RUN statements in this tutorial, so that I could see the difference in size. Here’s what I saw:
D:\Desktop\Learning Docker\03. Add A Package>docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my_clean_python latest a4f160c1518a 5 minutes ago 113MB
my_python latest 23c7fae3b613 7 minutes ago 135MB
The cleanup cut 22MB out of my image. That is a space savings of 16%. Cleanup pays.
To summarize, we learned how to launch a base image to see how it works. We covered how to use package managers to add new software into a container and an image. And we wrote a Dockerfile that created a new image with newly packaged software for use in a new image.
For the next lesson, we cover how to use our own image as a base image for a new project.