Improve your Software Development – Continuous Deployment with Docker – Part 5.2: Create and run a Docker image
Improve your Software Development – Continuous Deployment with Docker – Part 5.2: Create and run a Docker image
Hay Steemers,
let‘s continue our journey through the Docker cosmos. In the last part we learned what Docker is and we also learned how the Docker-Engine basically works. If you missed this part you can follow me here @dez1337, so this will never happen again.
In this part we will do some hands on work and install Docker, create and build our own image and start it as a Container. Lets go!
Introduction
To start a Container, we need an image which is, according to the last part of this tutorial series, a definition of how the Container should look like. So the first thing we do, is to define and create our own image.
Our Target
To do so, we need some plan of what we want to achieve. For this part, we want to create a Docker-Image, that contains an Apache2-Http-Server (Take a look here, if you don’t know what this program is), that can deliver the default HTML-Welcome page which looks like this by default:
Write a Docker-Image
Lets begin by creating a new file called “Dockerfile” (Take care that there is no extension like ‘.txt’ added to the filename) at a place of your choice.
Now open the file with a normal editor. I will use Notepad++ for Windows in this tutorial.
Before we start hacking our first Docker image now, we should think back to the list of things, that can be defined in a Docker-Image. In the last part, we created the following list of minimal questions, that we have to ask our self when we want to create an image and whose answers will be the definition of our container.
An Image basically contains the information about:
- What programs are needed in your Container?
- What ports does your Container need?
- Which command has to be executed, when the container starts?
- ...
Just keep in mind that there are much more points that can be defined in an image, but the ones from the list are enough for a simple container.
Let’s start with the first point “What programs are needed in your Container?”. We start completely from scratch, so we have to think about, what our Container has to contain, to be able to run an Apache2-Http-Server. And if we do this, we will find out that there is pretty much that we have to provide. For example: The application needs basic Operating-System features to write files, manage file permissions, execute files, and so on.
The best thing would be to start with a minimal Operating-System that provides all this features – But how do we do this in an Image? Do we have to add all the needed functionality manually?
To answer the question we will take a look on the layer-concept of Docker-Images and loose some words about Docker-Base images.
Image-Layers
A Docker-Image consists of several different layers. Each image can extend an existing image while only the changed files are saved as a new layer. The following picture hopefully makes that a bit clearer.
The picture shows an Image, that is build by 4 layers. To make it more clear, I will explain this by interpreting the image: The first layer is pretty huge, so maybe someone installed a bunch of software and build an image.
Another guy is looking for an image that contains exactly the software, the first guy has installed, but additionally needs a small tool. So he builds a new image, that is based on the image of the first guy and adds the tool he needs. Docker is intelligent enough to only store the changes of this new image. That’s why the second layer is only 194,5 kb small. This can be done over and over again.
We will use this concept to start our image based on an existing one, that already fits our needs. Before we do this in our Dockerfile, I want to answer the question “What do I do if there is no Image to extend?”. Well, if there is no image you want to extend, you have to create a so called “Base image”.
Base Images
In the last three years Docker has been used so much, that there basically is a base image for every need. Because of that, I will not describe how to create one in this tutorial, but for those who are interested in that, you can take a look at the official docker documentation.
Okay, based on the knowledge of the paragraphs above, we know that we want to extend an existing image that already contains all basic operating system features. In this tutorial, I will use Ubuntu 16.04, but you may find a better and smaller one if you spend some more effort in searching one.
But hay, where do we get this image from? Good question again and Docker also provides a solution for that: The Docker registry.
Docker registry
This Software basically provides a repository for Docker images and if you develop secret or private stuff, you can just setup your own registry in your own network. If it is okay for you, that everyone can use your image, you can also use the public Docker registry that is hosted by Docker and free to use. Just visit http://hub.docker.com and browse through thousands of public images.
When Docker gets installed, this public registry will be used by default to pull images and that’s the answer to the question of where do we get our Ubuntu image from. We just write the name and the version of the image in our Dockerfile and the Docker-Engine will do the rest for us.
To get the name and the version, just take a look at http://hub.docker.com, search for “Ubuntu” on the top right corner and choose one of the images.
Finally, we know everything to continue with our own image. The command to extend an existing image is “FROM {Company}[/{Name}][:Version]”. So we add:
FROM ubuntu:16.04
to our Dockerfile. (Just a hint: It is possible to define no specific version. In that case, docker will pull the "latest" image)
The next task is to install the Apache2-Http-Server. As we started with an Ubuntu, we can just use Ubuntus default packet manager called “apt-get” to install the package. To do so, we use the “RUN {Shell-Command}” command and add it to our Dockerfile.
RUN apt-get update && apt-get install apache2 -y
This will update the list of available software inside the container and force the installation of the apache2 package.
So step one is done – Great! Lets answer the next question “What ports does your Container need?”. The default port of the Apache2-Http-Server is port 80 and we just tell Docker that it has to open this port if we start this image. We do this under the use of the “EXPOSE {PORT}” command
EXPOSE 80
Quite easy, isn’t it? The last thing we do is to define the command, that docker should run when we start a container with our image. This is done by the “CMD {Start-Command}” command.
CMD service apache2 start && tail -F /var/log/apache2/error.log
We will start the apache2 service to make the Apache2-Http-Server running. Sadly, this isn’t enough as Docker will automatically kill the Container when the CMD-command has finished. So in this case, the Container would get killed immediately after the service has been started. To keep the Container alive, we use the Linux tail command to continuously print changes of the error.log to the console. This process will never end until we kill the container manually.
We are done! That was quite easy, or? The complete Dockerfile should now look like this:
FROM ubuntu:16.04
RUN apt-get update && apt-get install apache2 -y
EXPOSE 80
CMD service apache2 start && tail -F /var/log/apache2/error.log
Now let’s build the image with the Docker-Engine. If you haven’t installed it already, you can visit the official documentation and follow the steps for your operating system.
Build the image
After you have installed the Docker-Engine, it is quite easy to build the image. Just open a terminal/cmd and go to the folder that contains our Dockerfile. You can than execute the command below:
docker build . -t steemitExample
Docker should start to execute the things we have defined in our Dockerfile.
When the build has finished, you should have a local image tagged with the name “steemitExample”. To verify this, you can list all local images with the following command:
docker images
The result should contain the image we created seconds ago.
Run an image
Yeha! So far, we have created an image and build it. The last step is to start it as a Container, so just start another console/terminal windows and execute the following command:
docker run -p 80:80 steemitExample
The most parts of the command are pretty clear: “docker run” starts an image and “steemitExample” is the name of the image we have created. The only new thing is the “-p” Parameter, but don’t worry, it should be easy to understand. It just tells Docker, how to map the port of the Container to the ports of the host machine. We could change this to “-p 8080:80” to map port 8080 of the host to port 80 of the container – I hope that is clear.
Done! The Container is up and running – We will verify this by calling the webinterface with our browser. For Linux, just type the IP of the host machine into your browser. On Windows you have to take a look into the Docker-for-Windows configuration. For me, the IP was 10.0.75.2:
That is our first Docker-Image / Docker-Container. Good job!
Summary
Today we learned how to create our own image, where images came from and how to start our image - And WOW, that was more content than I initially thought it would be and we just saw the top of the iceberg. I hope you enjoyed our ride through the docker cosmos anyway and that I didn’t lost you somewhere, so just feel free to ask some questions or to leave a comment!
Our goal of a professional environment for software development is still not reached so stay tuned! You can follow me to never miss a new part.
Thank you for reading and best regards!
Forgot to add for windows (using powershell).
If you open a new shell, you have to tell your docker client how to connect to the docker daemon that is running in the virtualbox VM.
If you open the quickstart terminal, it runs a command that sets the shell environment up so that the docker client knows how to connect.
If you open a new shell, you have to do that step on your own. For windows, you have to specify your shell type. For cmd, run the following and then paste the output back into your cmd shell:
or for powershell, just run the following:
Thank you for pointing that out :) But I guess this was only needed if the the Docker toolbox has been used. If you use Docker for Windows (Available for Windows 10) you can start straight on the console without configuring things :)
Incredible post. Just spent 2 hours in discovery mode. Keep it coming