A Developer's Guide To Docker - The Dockerfile

avatar-leebrandt.jpg Lee Brandt

Creating a consistent environment for development, testing, staging, and production is one of the big benefits of using containers. Not only do containers make the entire environment portable, they remove environment-specific problems, like, “Why does it work in test, but not in production?” Usually, it’s a package or framework that’s installed on the test machine that is not on the production server. Containers carry all those dependencies with them, minimizing the possibility for those problems. To help create a consistent container, you need an image that is configured in code that can be versioned and distributed. That’s where the Dockerfile comes in.

A Dockerfile (without an extension) is simply a text file with some keywords and rules that Docker uses to create an image. That image is then used to create a container, or multiple containers that all have the same set up. In this tutorial, you’ll build a Dockerfile that you’ll use to create an image for a basic web application.

In the previous article in this series, I told you that images are like blueprints for creating containers. Well really, they are containers. Containers frozen in time that you can use to “stamp out a copy” anytime you want.

To get the base application, just clone it from: Github. This is just a basic Node website. Don’t have Node installed on your machine? Don’t worry, you’re not even going to run this application on your machine, you’re going to run it in a container.

Start with a Base Docker Image

Most of the time, you won’t start from scratch. You will create a Docker image based on another Docker image. The FROM line tells Docker what base image you want to use to build your new image. This must be the first line of the Dockerfile, you can have comments above it, but no other commands. In this case, you’ll be starting from the official node:8.4 image. So create a file called Dockerfile in the root folder of the application and add the FROM line right at the top:

FROM node:8.4

This tells Docker that we want to start from the official Node image tagged with the 8.4 version. This comes with a Linux system base (in this case Debian Jessie), and adds Node and NPM to the image.

Get your Node App into the Image

Next, you’ll run some commands to get your app (and it’s dependencies) into the image you’re creating.

COPY . /app

This COPY command just copies everything from the current directory (since your Dockerfile is in the root folder of your node application) to a folder called /app inside the image you’re creating.

Next, you’ll set the working directory in the Dockerfile.

WORKDIR /app

This tells Docker that the rest of the commands will be run in the context of the /app folder inside the image. Next, you’ll add a RUN command to get the application’s dependencies:

RUN ["npm", "install"]

You might be thinking, “That’s a really weird way to run things!”

This style of RUN command in a Dockerfile is called the “exec form”. You can write these commands in “shell form”, like so:

RUN npm install

Use the exec form to avoid the image’s shell munging string arguments. If your shell command relies on a specific shell and you are not sure if the shell you need is available on the image you’re using. You can use the SHELL command to change the shell that a shell form command will run in.

Overall, this command will restore all the NPM packages for your project.

Expose and Run Your Node App

Next, you’ll open up port 3000 on TCP (where our app runs), to the outside world.

EXPOSE 3000/tcp

Lastly, you’ll run the application in the container. Remember that Docker is meant to be one-to-one, container to application, so when building this container it is only natural that we have a command that we want to run that will get our application running in the container. To do this, we need to run a CMD command. Whatever is run by the CMD command will be run at Process ID 1 (PID1) in the container. As long as whatever runs at PID1 in the container is running, the container is running.

CMD ["npm", "start"]

You could also use the ENTRYPOINT command in the Dockerfile, but either work and you will see the ENTRYPOINT command in the next post on docker-compose.

Your whole Dockerfile is six lines long. The FROM line starts from a base image that gives you most of what you need, then copies your code to the image and runs a few commands to get dependencies and compile the app. Then opens port 5000 to listen for requests.

Meet Your Dockerfile

The finished Dockerfile:

FROM node:8.4

COPY . /app

WORKDIR /app

RUN ["npm", "install"]

EXPOSE 3000/tcp

CMD ["npm", "start"]

I like to put one line of space between the lines in Dockerfiles because I think it helps with readability and because most examples I’ve read do it that way.

From the directory where the Dockerfile is, simply run

docker build -t tutorial:0.0.1 .

Just like when pulling images from Dockerhub, this command tells the Docker engine to create a repository named “tutorial” and tag it with “0.0.1”.

When it’s finished, you can run:

docker image list

You’ll see the image in your list named tutorial with a tag of 0.0.1. If you want to create a container from this image and run it, run the command:

docker run -p 3000:3000 -d --name demo tutorial:0.0.1  

This will create a container based on the tutorial:0.0.1 image that you just creted and name it ‘demo’. This command also has the -d switch that specifies that you want to run it in daemon mode (in the background). Finally, it also has the -p switch that maps port 3000 on the host machine (your local machine) to the exposed port on the container (formatted like [host port]:[container port]). This will allow you to go to http://localhost:3000 on your machine and be viewing the container’s response on that same port.

Learn More

Congratulations! You just built your first container from a base image and added your application to it! As you can see, it’s easy to put together a container when you find the right base image to build from.

Obviously, there are a lot of other things the Dockerfile can do for you. To find out more about what you can do in a Dockerfile check out the documentation.

Now that you’ve learned the basics of Docker and built your first Dockerfile, you’re ready to start composing containers and delivering those containers to production!

If you have any questions, comments, or suggestions, feel free to reach out to me via email, or hit me up in the comments or via Twitter @leebrandt.