Building Custom Container Images with Dockerfiles
Containers - why all the hype? If you're looking for answers then this course is for you!
"Deploying Containerized Applications Technical Overview" provides an in-depth review of containers and why they've become so popular. Complementing the technical discussions, several hands-on demonstrations are provided, enabling you to learn about the concepts of containerization by seeing them in action.
You'll also learn about containerizing applications and services, testing them using Docker, and deploying them into a Kubernetes cluster using Red Hat OpenShift.
In this video we're going to talk about How You Can Create Your Custom Images Using Dockerfiles. So I talked earlier about how images are sort of like recipes. Well a Dockerfile is going to be like the recipe but it has all of the instructions laid out for how you're going to create your container. So the steps that you need to do in order to create a Dockerfile are first set you need to create your own working directory.
So the working directory is just going to be a directory that has all the files that you want to have packaged into your image. One thing to note is you don't want to make the working directory something like your root directory because when you try to build this image it's going to try to index everything, it's going to create all sorts of security vulnerabilities, If you tried to just use your root directory. So be sure to make a specific space for this Dockerfile. Then you're in that directory you're going to put a file with the name Dockerfile with a capital D and then you're going to have all of your instructions and then you just use the podman build command in order to create your image. So like I said Dockerfile is just a simple text file and each line is just going to have an instruction and then arguments for that instruction and we'll take a look at some examples in a second.
One thing to note about the Dockerfile is that each instruction is executed sequentially, so it's really important to understand what is happening when you don't want to just randomly put in all of your yum installs and then put all your copies in without really thinking about okay, these are going to be happening in order. So be very deliberate about the order in which you put your instructions.
Okay, so here's a sample Dockerfile, as promised. Just stepping through some of these instructions the ones in the items in bold on the left that are in all capitals those are the types of instructions. So the first instruction you'll see well the first thing you see is a the comment line, so that is how you put comments in a Dockerfile. It's a good idea to comment lines if things might be a little unclear we really want to make these Dockerfiles very readable, they're meant to be read by other people.
So the first instruction we see after the comment is the FROM instruction, so that is going to set what our base image or apparent image is going to be. So think back to that mysql image I just deployed. The mysql image was inheriting a rhel parent image. So what that means is we were installing rhel and then on top of that we were installing mysql. So in the case of this Dockerfile this is an Apache Dockerfile, you can skip down, you can see the description it says, this is a custom Apache container image.
We are building we are installing rhel and then we were building Apache on top of it. The next instruction is LABEL and Label is just a simple key value pair that you can use. This is helpful for later on if you're wanting to filter and search through your containers, this is just metadata it's not going to have any effect on the outcome of what's installed or what your container looks like. Same thing with Maintainer that's also just metadata it's a good idea to put your name and email address there in the Dockerfile.
The RUN command is where you're executing the Run instruction, excuse me, is where you're executing commands, so in this case we are doing a Run and we're just doing a yum install of Apache. EXPOSE is communicating to the developer which port is being exposed, 80 is the default port for Apache, so it's important to note that exposed doesn't actually do anything. It's really is just more metadata so you're communicating to the developer hey you need to make sure that you're aware that this is the port that we are going to be listening for. ENV is setting environment variables, so any environment variables you configure here are going to be propagated down into your container.
The ADD command is able to put files from remote places inside of your container, so in this case this example is just pulling a PDF and then copying it into the var/www/html directory which is where the Apache server is serving files. Similarly the COPY command can also put things from the host directory into the container, a theory is that add is able to do remote files and is also able to untar files and unpackage files. So if you have a tar, you wanted to copy in the container if you use add it will help untar that file.
The USER command is what you would use to specify which user you want to Run the subsequent commands as. As I mentioned all of this is happening sequentially in order, so the user command if you put that before the add or the copy or the run it would try to execute those commands as the user apache in this case it's happening afterward, so that means the entry point is going to be run as the user apache.
Okay, ENTRYPOINT again containers are isolated processes and in this case this container is going to be running the httpd command provides the parameters for the entry point, so the only difference here is that we are running this in the foreground. Just a little bit more about command ENTRYPOINT, I realize that these concepts can be a little bit confusing but I like I said ENTRYPOINT helps define Dockerfiles and their images and the containers that create as executables. So by default an ENTRYPOINT is specified as bin/sh -c so it's now required that you put in ENTRYPOINT is just a good idea. Command is going to provide the arguments for that image so if you don't have an ENTRYPOINT but that you placed a command something like a ping or something like that as your command it's still going to execute just normally as a ping.
The one advantage is that you can override that command and that parameter by passing in a command on your RUN instruction. So this is nice because it helps make your containers really flexible, so it's just a good idea to have an ENTRYPOINT in command if you can because you are then able to override that command as you need. Image Layering, so another important aspect of Dockerfiles is that you want to be very aware of how they're written and you want to make them as readable and as efficient as possible. So one thing to know is that every time you do a RUN instruction like for example, down here we're running three yum run instructions in a row.
So what's that doing is that it's creating a layer to build that image every single time we do a separate instruction. It can be as a minor change but you know if your images get bigger and bigger this quickly helps expand the size to be much bigger than you would want it to be. It's not unheard of for images to reach you know up in the eight hundred nine hundred one gig to Gig sizes. So you really want to be cognizant of keeping your Dockerfiles as slim as possible. So we can get around that if we use these double ampersands. So what this is doing is allowing us to use a single RUN instruction but we're able to run multiple commands. So this is great because we ultimately end up with one layer compared to three layers in the previous example. One thing about that however is that it's not super readable so again we want our images to not only be very efficient but we want them to be readable as well. So we can get around that by using these back slashes here and that will allow us to both stack our RUN instructions, so that they look just as good as the very first example but they're also going to be efficient and only create a single layer.
That kind of formatting works really well for LABEL and environment variables to you. So very often see that there's a lot of labels stacked right at the top of your Dockerfiles and the same thing for environment variables it helps a lot to make it more readable if you put them onto new lines. So once we have written our Dockerfile we can use podman build to actually create that image and have it stored locally.
So the syntax in order to do that is to run podman build -t we'll give it a NAME: and give it a TAG and then you point to the directory where the Dockerfile is located. So one of the things you'll see very often is that most people are doing podman builds within the current directory just be aware that often times that current directory is represented as just a single period. So just be aware a lot of times when I'm copying out commands I've sometimes forget to put that single period, but it's very simple just to build the Dockerfile into an image once you have that available. Okay, so let's go ahead and hop into a demonstration and I'll show you how to build a Dockerfile.
Okay, so I am here in my working directory which is called dockerfile-create and I am going to open up a vi session and create a Docker file. So the first thing we want to do is we want to specify our parent image in this case rhel7 and I'm going to use the tag 7.5 and we're going to be creating an Apache image here I should mention. Okay, I want to specify the MAINTAINER which is means Zach Gutterman, here's my email, you can send me compliments or hate mail, if you would like and we're going to add a LABEL remember this is just metadata at this point, so these are just simple key value pairs so the description say "A custom Apache image".
Okay, I'm going to do an ADD, this is a file that I already have ready to go and all this is doing is configuring my yum.repository that's going to work in this classroom environment but what I'm doing is I'm copying this training.repo file, that's in my working directory and I'm copying it into my container and I want to do this first because remember this is all happening sequentially.
So my next instruction is that I'm going to do a yum install of Apache and I'm doing a double ampersand and I'm going to do a yum clean all remember. This is two yum instructions, there are two yum runs that are happening. I'm only using a single run and that's going to make it so that I only have one added layer to my image. Okay and I'm going to do a further customization here I'm going to do a RUN echo Hello from Dockerfile and I'm going to put that in the usr/share/httpd/noindex/index.html this way when we actually hit the webserver, we should see this message that will confirm that our server has indeed been customized.
Okay and then I'm going to run an EXPOSE 80. This is going to expose the port actually it's not going to expose the port it's this is just metadata which says that the port is exposed.
So again it doesn't actually do anything but we know that Apache by default runs on port 80, so we're letting other developers know the same and we are going to put an ENTRYPOINT that is going to specify httpd and I'm going to add some parameters. Lkay, alright let's just double check to make sure everything looks good there. ENTRYPOINT, probably want to put a space here, ENTRYPOINT expose run okay, that looks good to me so let's give it a shot let's see if it works. Okay, so we're already in our directory and I'm going to run a podman build -t i wanna name it do80/apache and I'm using that period, to say that I'm in this directory already. So you know my Dockerfile is here and you can go ahead and try to build it.
Okay, so once that returns if we run sudo podman images, we're going to see not only do we have our Apache image but we've got a rhel7 image because remember that is our parent image, the pod man knows that it needs to download that image as well in addition to our custom DO080 apache image. Okay, so now we can try to run it and see what happens.
I give this the name lab-apache. I'm going to run it in the background with a -d -9 option I'm going to use something we haven't seen yet before which is -p that is a port forward, all that's going to do is enable me to access the web server from the localhost, so I'm forwarding port 10080 to port 80 within the container, remember that the container is running on port 80 so if I any try any network traffic that's going to localhost or to I guess anything on port 10080 is going to forward traffic into port 80 within the container and then do180 apache the name of our image.
Oh, I'm sorry it's do080. Okay, so sudo podman ps, okay, we see that our Apache container is up and running. So let's give it a shot I will try and do a curl port 10080 remember that's the port that we're forwarding from Hello from Dockerfile.
Okay great, we did it so that concludes this video and I'll see you in the next video and we'll start talking more about OpenShift
About the Author
Jeremy is the DevOps Content Lead at Cloud Academy where he specializes in developing technical training documentation for DevOps.
He has a strong background in software engineering, and has been coding with various languages, frameworks, and systems for the past 20+ years. In recent times, Jeremy has been focused on DevOps, Cloud, Security, and Machine Learning.
Jeremy holds professional certifications for both the AWS and GCP cloud platforms.