In the previous article in this series we pulled a Docker container from DockerHub and started it on our local machine. This can be quite useful if you are trying to work with something like a database in your local development environment but if you want to customize or run your own code within a Docker container you will have to create your own Docker image.
The first step to creating your own Docker image is writing up a Dockerfile. A Dockerfile is like a recipe Docker uses to build a Docker image. In this article we are going to look at the Dockerfile used to build the container from Docker Series — Starting your first image and talk through what each command is doing. By the end you will be ready to build your own Dockerfile.
A Dockerfile is a text document used to indicate to Docker a base image, the Docker settings you need, and a list of commands you would like to have executed to prepare and start your new container.
When a brand new Docker operating system image is created from scratch the Docker engine starts out with a blank base image. Docker runs commands one by one specified in the Operating System Dockerfile. Each command creates a new layer for the image and at the end of the Docker build we have a new Docker image made up of a stack of layers.
From there if we wanted to build another new Docker image we can leverage the existing Operating System Docker image as a starting point for the new build. This is called the base image. From this base image the Docker engine runs the commands in the Dockerfile creating new layers, stacking them on top of the layers created for the Operating system image.
In the end you can think of our final Docker image as a result of stacking different Docker image layers on top of each other from the Operating System, to the Application Framework, to the Application itself.
To help us get started understanding and writing Dockerfiles I have created a pintail-whoami project on GitHub. There you will find a file called Dockerfile which is also shown below. We will go though this Dockerfile line by line to help you understand what is happening and give you the tools you need to write your own Dockerfile.
FROM node:9.3.0-alpineRUN npm install -g @firstname.lastname@example.org \
&& mkdir -p /usr/src/pintail-whoamiWORKDIR /usr/src/pintail-whoamiADD . /usr/src/pintail-whoamiRUN npm install && ng buildEXPOSE 80CMD node server.js $HOSTNAME
As you may have noticed the syntax of a Dockerfile is very simple and will always follow the pattern below:
The first thing you will do when you start creating your Dockerfile is in indicate the base image. Your base image can be any existing Docker image but typically you will start with an operating system image or a specific application framework. For the pintail-whoami Docker image I am using node.js.
FROM is the Dockerfile instruction to indicate the base image. For this Docker image we are using the officially published node.js image on Docker Hub. Also notice the version of the Docker image we are using 9.3.0-alpine. From this version name you can see we will be using node.js 9.3.0 running on top of the linux alpine operating system.
Be sure to always indicate a specific version of the base image you would like to use because you never know when the ‘Latest’ image will be changed.
RUN npm install -g @email@example.com \
&& mkdir -p /usr/src/pintail-whoami
The RUN instruction signals to Docker that the following command should be run as a normal linux commands during the Docker build. This RUN instruction installs the Angular cli and creates a directory for the pintail-whoami files to be placed.
Always try to group together logically similar Linux commands under as few RUN instructions as possible to save the overhead caused by too many separate layers.
The WORKDIR instruction indicates the location from which the arguments following the CMD Dockerfile instruction will be run, any subsequent RUN commands, and it also indicates the location where you would enter the Docker container if you were to create an executable shell.
More about executable shells in future Docker blogs
ADD . /usr/src/pintail-whoami
The ADD instruction is used to indicated to Docker what files from you current build environment you would like to add to your docker image and where you would like them located. In this case the “.” tells docker to include all files from the directory you run the build command. For this pintail-whoami project the Dockerfile is located at the top of the project structure so we will be including all project files in out Docker image. Within the Docker image all of these files will be stored in the “/usr/src/pintail-whoami” directory as indicated by the second parameter in the ADD instruction.
RUN npm install && ng build
As mentioned before, the RUN command executes the argument. This command is going to run within the pintail-whoami directory, it will gather all the project dependencies, and build the Angular project.
This EXPOSE instruction exposes the port to the host operating system making it easy to see what port on the Docker container should be bound out to the host OS.
CMD node server.js $HOSTNAME
Now the star of the show! The CMD instruction inducates the Linux command that will be run when the Docker container starts up. This command will become process 0 on the Docker container and the Docker container will continue to run as long as this process is running. Once the process ends, the container will stop. For this example, we start the node server and pass in the Docker container hostname as an argument.
Dockerfiles can be a little intimidating at first but after you get a hang of it they become powerful and easy to use. Dockerfiles are also nice because they are self documenting! All of the steps you need to run to setup the container are listed in order in the Dockerfile and anyone can go take a look. Just think of a Dockerfile as a recipe for a Docker image.
If you want to know more take a look at the official documentation here.
Docker Series — What is Docker?
Docker Series — Starting your first container
Docker Series — Building your first Docker image
Docker Series — Moving past one container with Docker Compose
Docker Series — Cheat Sheet
Docker Series — Moving past one machine with Docker Swarm and Kubernetes